5 * Contains \Drupal\Tests\Core\Render\RendererTestBase.
8 namespace Drupal\Tests\Core\Render;
10 use Drupal\Core\Cache\Cache;
11 use Drupal\Core\Cache\CacheableMetadata;
12 use Drupal\Core\Cache\Context\ContextCacheKeys;
13 use Drupal\Core\Cache\MemoryBackend;
14 use Drupal\Core\Render\PlaceholderGenerator;
15 use Drupal\Core\Render\PlaceholderingRenderCache;
16 use Drupal\Core\Render\Renderer;
17 use Drupal\Tests\UnitTestCase;
18 use Symfony\Component\DependencyInjection\ContainerBuilder;
19 use Symfony\Component\HttpFoundation\Request;
20 use Symfony\Component\HttpFoundation\RequestStack;
23 * Base class for the actual unit tests testing \Drupal\Core\Render\Renderer.
25 abstract class RendererTestBase extends UnitTestCase {
28 * The tested renderer.
30 * @var \Drupal\Core\Render\Renderer
35 * The tested render cache.
37 * @var \Drupal\Core\Render\PlaceholderingRenderCache
39 protected $renderCache;
42 * The tested placeholder generator.
44 * @var \Drupal\Core\Render\PlaceholderGenerator
46 protected $placeholderGenerator;
49 * @var \Symfony\Component\HttpFoundation\RequestStack
51 protected $requestStack;
54 * @var \Drupal\Core\Cache\CacheFactoryInterface|\PHPUnit_Framework_MockObject_MockObject
56 protected $cacheFactory;
59 * @var \Drupal\Core\Cache\Context\CacheContextsManager|\PHPUnit_Framework_MockObject_MockObject
61 protected $cacheContexts;
64 * The mocked controller resolver.
66 * @var \Drupal\Core\Controller\ControllerResolverInterface|\PHPUnit_Framework_MockObject_MockObject
68 protected $controllerResolver;
71 * The mocked theme manager.
73 * @var \Drupal\Core\Theme\ThemeManagerInterface|\PHPUnit_Framework_MockObject_MockObject
75 protected $themeManager;
78 * The mocked element info.
80 * @var \Drupal\Core\Render\ElementInfoManagerInterface|\PHPUnit_Framework_MockObject_MockObject
82 protected $elementInfo;
85 * @var \Drupal\Core\Cache\CacheBackendInterface
87 protected $memoryCache;
90 * The simulated "current" user role, for use in tests with cache contexts.
94 protected $currentUserRole;
97 * The mocked renderer configuration.
101 protected $rendererConfig = [
102 'required_cache_contexts' => [
103 'languages:language_interface',
106 'auto_placeholder_conditions' => [
108 'contexts' => ['session', 'user'],
109 'tags' => ['current-temperature'],
116 protected function setUp() {
119 $this->controllerResolver = $this->getMock('Drupal\Core\Controller\ControllerResolverInterface');
120 $this->themeManager = $this->getMock('Drupal\Core\Theme\ThemeManagerInterface');
121 $this->elementInfo = $this->getMock('Drupal\Core\Render\ElementInfoManagerInterface');
122 $this->elementInfo->expects($this->any())
124 ->willReturnCallback(function ($type) {
127 $info = ['#theme_wrappers' => ['details']];
130 $info = ['#theme' => 'link'];
135 $info['#defaults_loaded'] = TRUE;
138 $this->requestStack = new RequestStack();
139 $request = new Request();
140 $request->server->set('REQUEST_TIME', $_SERVER['REQUEST_TIME']);
141 $this->requestStack->push($request);
142 $this->cacheFactory = $this->getMock('Drupal\Core\Cache\CacheFactoryInterface');
143 $this->cacheContextsManager = $this->getMockBuilder('Drupal\Core\Cache\Context\CacheContextsManager')
144 ->disableOriginalConstructor()
146 $this->cacheContextsManager->method('assertValidTokens')->willReturn(TRUE);
147 $current_user_role = &$this->currentUserRole;
148 $this->cacheContextsManager->expects($this->any())
149 ->method('convertTokensToKeys')
150 ->willReturnCallback(function($context_tokens) use (&$current_user_role) {
152 foreach ($context_tokens as $context_id) {
153 switch ($context_id) {
155 $keys[] = 'r.' . $current_user_role;
157 case 'languages:language_interface':
164 $keys[] = $context_id;
167 return new ContextCacheKeys($keys, new CacheableMetadata());
169 $this->placeholderGenerator = new PlaceholderGenerator($this->rendererConfig);
170 $this->renderCache = new PlaceholderingRenderCache($this->requestStack, $this->cacheFactory, $this->cacheContextsManager, $this->placeholderGenerator);
171 $this->renderer = new Renderer($this->controllerResolver, $this->themeManager, $this->elementInfo, $this->placeholderGenerator, $this->renderCache, $this->requestStack, $this->rendererConfig);
173 $container = new ContainerBuilder();
174 $container->set('cache_contexts_manager', $this->cacheContextsManager);
175 $container->set('render_cache', $this->renderCache);
176 $container->set('renderer', $this->renderer);
177 \Drupal::setContainer($container);
181 * Generates a random context value for the placeholder tests.
183 * The #context array used by the placeholder #lazy_builder callback will
184 * generally be used to provide metadata like entity IDs, field machine names,
185 * paths, etc. for JavaScript replacement of content or assets. In this test,
186 * the #lazy_builder callback PlaceholdersTest::callback() renders the context
187 * inside test HTML, so using any random string would sometimes cause random
188 * test failures because the test output would be unparseable. Instead, we
189 * provide random tokens for replacement.
191 * @see PlaceholdersTest::callback()
192 * @see https://www.drupal.org/node/2151609
194 protected function randomContextValue() {
195 $tokens = ['llama', 'alpaca', 'camel', 'moose', 'elk'];
196 return $tokens[mt_rand(0, 4)];
200 * Sets up a render cache back-end that is asserted to be never used.
202 protected function setUpUnusedCache() {
203 $this->cacheFactory->expects($this->never())
208 * Sets up a memory-based render cache back-end.
210 protected function setupMemoryCache() {
211 $this->memoryCache = $this->memoryCache ?: new MemoryBackend();
213 $this->cacheFactory->expects($this->atLeastOnce())
216 ->willReturn($this->memoryCache);
220 * Sets up a request object on the request stack.
222 * @param string $method
223 * The HTTP method to use for the request. Defaults to 'GET'.
225 protected function setUpRequest($method = 'GET') {
226 $request = Request::create('/', $method);
227 // Ensure that the request time is set as expected.
228 $request->server->set('REQUEST_TIME', (int) $_SERVER['REQUEST_TIME']);
229 $this->requestStack->push($request);
233 * Asserts a render cache item.
236 * The expected cache ID.
238 * The expected data for that cache ID.
240 * The expected cache bin.
242 protected function assertRenderCacheItem($cid, $data, $bin = 'render') {
243 $cache_backend = $this->cacheFactory->get($bin);
244 $cached = $cache_backend->get($cid);
245 $this->assertNotFalse($cached, sprintf('Expected cache item "%s" exists.', $cid));
246 if ($cached !== FALSE) {
247 $this->assertEquals($data, $cached->data, sprintf('Cache item "%s" has the expected data.', $cid));
248 $this->assertSame(Cache::mergeTags($data['#cache']['tags'], ['rendered']), $cached->tags, "The cache item's cache tags also has the 'rendered' cache tag.");
255 class PlaceholdersTest {
258 * #lazy_builder callback; attaches setting, generates markup.
260 * @param string $animal
264 * A renderable array.
266 public static function callback($animal, $use_animal_as_array_key = FALSE) {
268 if ($use_animal_as_array_key) {
269 $value = [$animal => TRUE];
272 '#markup' => '<p>This is a rendered placeholder!</p>',
274 'drupalSettings' => [
275 'dynamic_animal' => $value,
282 * #lazy_builder callback; attaches setting, generates markup, user-specific.
284 * @param string $animal
288 * A renderable array.
290 public static function callbackPerUser($animal) {
291 $build = static::callback($animal);
292 $build['#cache']['contexts'][] = 'user';
297 * #lazy_builder callback; attaches setting, generates markup, cache tag.
299 * @param string $animal
303 * A renderable array.
305 public static function callbackTagCurrentTemperature($animal) {
306 $build = static::callback($animal);
307 $build['#cache']['tags'][] = 'current-temperature';