3 namespace Drupal\Tests\Core\Utility;
5 use Drupal\Component\Utility\Html;
6 use Drupal\Core\Cache\Context\CacheContextsManager;
7 use Drupal\Core\DependencyInjection\ContainerBuilder;
8 use Drupal\Core\Language\LanguageInterface;
9 use Drupal\Core\Render\BubbleableMetadata;
10 use Drupal\Core\Render\Markup;
11 use Drupal\Core\Utility\Token;
12 use Drupal\Tests\UnitTestCase;
15 * @coversDefaultClass \Drupal\Core\Utility\Token
18 class TokenTest extends UnitTestCase {
21 * The cache used for testing.
23 * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject
28 * The language manager used for testing.
30 * @var \Drupal\Core\Language\LanguageManagerInterface|\PHPUnit_Framework_MockObject_MockObject
32 protected $languageManager;
35 * The module handler service used for testing.
37 * @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
39 protected $moduleHandler;
42 * The language interface used for testing.
44 * @var \Drupal\Core\Language\LanguageInterface|\PHPUnit_Framework_MockObject_MockObject
49 * The token service under test.
51 * @var \Drupal\Core\Utility\Token|\PHPUnit_Framework_MockObject_MockObject
56 * The cache tags invalidator.
58 * @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface|\PHPUnit_Framework_MockObject_MockObject
60 protected $cacheTagsInvalidator;
63 * The cache contexts manager.
65 * @var \Drupal\Core\Cache\Context\CacheContextsManager
67 protected $cacheContextManager;
72 * @var \Drupal\Core\Render\RendererInterface|\PHPUnit_Framework_MockObject_MockObject
79 protected function setUp() {
80 $this->cache = $this->getMock('\Drupal\Core\Cache\CacheBackendInterface');
82 $this->languageManager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface');
84 $this->moduleHandler = $this->getMock('\Drupal\Core\Extension\ModuleHandlerInterface');
86 $this->language = $this->getMock('\Drupal\Core\Language\LanguageInterface');
88 $this->cacheTagsInvalidator = $this->getMock('\Drupal\Core\Cache\CacheTagsInvalidatorInterface');
90 $this->renderer = $this->getMock('Drupal\Core\Render\RendererInterface');
92 $this->token = new Token($this->moduleHandler, $this->cache, $this->languageManager, $this->cacheTagsInvalidator, $this->renderer);
94 $container = new ContainerBuilder();
95 $this->cacheContextManager = new CacheContextsManager($container, [
99 $container->set('cache_contexts_manager', $this->cacheContextManager);
100 \Drupal::setContainer($container);
106 public function testGetInfo() {
110 'name' => $this->randomMachineName(),
115 $this->language->expects($this->atLeastOnce())
117 ->will($this->returnValue($this->randomMachineName()));
119 $this->languageManager->expects($this->once())
120 ->method('getCurrentLanguage')
121 ->with(LanguageInterface::TYPE_CONTENT)
122 ->will($this->returnValue($this->language));
124 // The persistent cache must only be hit once, after which the info is
125 // cached statically.
126 $this->cache->expects($this->once())
128 $this->cache->expects($this->once())
130 ->with('token_info:' . $this->language->getId(), $token_info);
132 $this->moduleHandler->expects($this->once())
133 ->method('invokeAll')
135 ->will($this->returnValue($token_info));
136 $this->moduleHandler->expects($this->once())
138 ->with('token_info', $token_info);
140 // Get the information for the first time. The cache should be checked, the
141 // hooks invoked, and the info should be set to the cache should.
142 $this->token->getInfo();
143 // Get the information for the second time. The data must be returned from
144 // the static cache, so the persistent cache must not be accessed and the
145 // hooks must not be invoked.
146 $this->token->getInfo();
152 public function testReplaceWithBubbleableMetadataObject() {
153 $this->moduleHandler->expects($this->any())
154 ->method('invokeAll')
155 ->willReturn(['[node:title]' => 'hello world']);
157 $bubbleable_metadata = new BubbleableMetadata();
158 $bubbleable_metadata->setCacheContexts(['current_user']);
159 $bubbleable_metadata->setCacheMaxAge(12);
161 $node = $this->prophesize('Drupal\node\NodeInterface');
162 $node->getCacheTags()->willReturn(['node:1']);
163 $node->getCacheContexts()->willReturn(['custom_context']);
164 $node->getCacheMaxAge()->willReturn(10);
165 $node = $node->reveal();
167 $result = $this->token->replace('[node:title]', ['node' => $node], [], $bubbleable_metadata);
168 $this->assertEquals('hello world', $result);
170 $this->assertEquals(['node:1'], $bubbleable_metadata->getCacheTags());
171 $this->assertEquals([
174 ], $bubbleable_metadata->getCacheContexts());
175 $this->assertEquals(10, $bubbleable_metadata->getCacheMaxAge());
181 public function testReplaceWithHookTokensWithBubbleableMetadata() {
182 $this->moduleHandler->expects($this->any())
183 ->method('invokeAll')
184 ->willReturnCallback(function ($hook_name, $args) {
185 $cacheable_metadata = $args[4];
186 $cacheable_metadata->addCacheContexts(['custom_context']);
187 $cacheable_metadata->addCacheTags(['node:1']);
188 $cacheable_metadata->setCacheMaxAge(10);
190 return ['[node:title]' => 'hello world'];
193 $node = $this->prophesize('Drupal\node\NodeInterface');
194 $node->getCacheContexts()->willReturn([]);
195 $node->getCacheTags()->willReturn([]);
196 $node->getCacheMaxAge()->willReturn(14);
197 $node = $node->reveal();
199 $bubbleable_metadata = new BubbleableMetadata();
200 $bubbleable_metadata->setCacheContexts(['current_user']);
201 $bubbleable_metadata->setCacheMaxAge(12);
203 $result = $this->token->replace('[node:title]', ['node' => $node], [], $bubbleable_metadata);
204 $this->assertEquals('hello world', $result);
205 $this->assertEquals(['node:1'], $bubbleable_metadata->getCacheTags());
206 $this->assertEquals([
209 ], $bubbleable_metadata->getCacheContexts());
210 $this->assertEquals(10, $bubbleable_metadata->getCacheMaxAge());
217 public function testReplaceWithHookTokensAlterWithBubbleableMetadata() {
218 $this->moduleHandler->expects($this->any())
219 ->method('invokeAll')
222 $this->moduleHandler->expects($this->any())
224 ->willReturnCallback(function ($hook_name, array &$replacements, array $context, BubbleableMetadata $bubbleable_metadata) {
225 $replacements['[node:title]'] = 'hello world';
226 $bubbleable_metadata->addCacheContexts(['custom_context']);
227 $bubbleable_metadata->addCacheTags(['node:1']);
228 $bubbleable_metadata->setCacheMaxAge(10);
231 $node = $this->prophesize('Drupal\node\NodeInterface');
232 $node->getCacheContexts()->willReturn([]);
233 $node->getCacheTags()->willReturn([]);
234 $node->getCacheMaxAge()->willReturn(14);
235 $node = $node->reveal();
237 $bubbleable_metadata = new BubbleableMetadata();
238 $bubbleable_metadata->setCacheContexts(['current_user']);
239 $bubbleable_metadata->setCacheMaxAge(12);
241 $result = $this->token->replace('[node:title]', ['node' => $node], [], $bubbleable_metadata);
242 $this->assertEquals('hello world', $result);
243 $this->assertEquals(['node:1'], $bubbleable_metadata->getCacheTags());
244 $this->assertEquals([
247 ], $bubbleable_metadata->getCacheContexts());
248 $this->assertEquals(10, $bubbleable_metadata->getCacheMaxAge());
252 * @covers ::resetInfo
254 public function testResetInfo() {
255 $this->cacheTagsInvalidator->expects($this->once())
256 ->method('invalidateTags')
257 ->with(['token_info']);
259 $this->token->resetInfo();
264 * @dataProvider providerTestReplaceEscaping
266 public function testReplaceEscaping($string, array $tokens, $expected) {
267 $this->moduleHandler->expects($this->any())
268 ->method('invokeAll')
269 ->willReturnCallback(function ($type, $args) {
270 return $args[2]['tokens'];
273 $result = $this->token->replace($string, ['tokens' => $tokens]);
274 $this->assertInternalType('string', $result);
275 $this->assertEquals($expected, $result);
278 public function providerTestReplaceEscaping() {
281 // No tokens. The first argument to Token::replace() should not be escaped.
282 $data['no-tokens'] = ['muh', [], 'muh'];
283 $data['html-in-string'] = ['<h1>Giraffe</h1>', [], '<h1>Giraffe</h1>'];
284 $data['html-in-string-quote'] = ['<h1>Giraffe"</h1>', [], '<h1>Giraffe"</h1>'];
286 $data['simple-placeholder-with-plain-text'] = ['<h1>[token:meh]</h1>', ['[token:meh]' => 'Giraffe"'], '<h1>' . Html::escape('Giraffe"') . '</h1>'];
288 $data['simple-placeholder-with-safe-html'] = [
289 '<h1>[token:meh]</h1>',
290 ['[token:meh]' => Markup::create('<em>Emphasized</em>')],
291 '<h1><em>Emphasized</em></h1>',