1<?php 2 3namespace Drupal\KernelTests\Core\Theme; 4 5use Drupal\Core\Path\CurrentPathStack; 6use Drupal\Core\Path\PathMatcherInterface; 7use Drupal\Core\Routing\RouteMatchInterface; 8use Drupal\Core\Theme\Registry; 9use Drupal\Core\Utility\ThemeRegistry; 10use Drupal\KernelTests\KernelTestBase; 11 12/** 13 * Tests the behavior of the ThemeRegistry class. 14 * 15 * @group Theme 16 */ 17class RegistryTest extends KernelTestBase { 18 19 /** 20 * Modules to enable. 21 * 22 * @var array 23 */ 24 public static $modules = ['theme_test', 'system']; 25 26 protected $profile = 'testing'; 27 28 /** 29 * Tests the behavior of the theme registry class. 30 */ 31 public function testRaceCondition() { 32 // The theme registry is not marked as persistable in case we don't have a 33 // proper request. 34 \Drupal::request()->setMethod('GET'); 35 $cid = 'test_theme_registry'; 36 37 // Directly instantiate the theme registry, this will cause a base cache 38 // entry to be written in __construct(). 39 $cache = \Drupal::cache(); 40 $lock_backend = \Drupal::lock(); 41 $registry = new ThemeRegistry($cid, $cache, $lock_backend, ['theme_registry'], $this->container->get('module_handler')->isLoaded()); 42 43 $this->assertNotEmpty(\Drupal::cache()->get($cid), 'Cache entry was created.'); 44 45 // Trigger a cache miss for an offset. 46 $this->assertNotEmpty($registry->get('theme_test_template_test'), 'Offset was returned correctly from the theme registry.'); 47 // This will cause the ThemeRegistry class to write an updated version of 48 // the cache entry when it is destroyed, usually at the end of the request. 49 // Before that happens, manually delete the cache entry we created earlier 50 // so that the new entry is written from scratch. 51 \Drupal::cache()->delete($cid); 52 53 // Destroy the class so that it triggers a cache write for the offset. 54 $registry->destruct(); 55 56 $this->assertNotEmpty(\Drupal::cache()->get($cid), 'Cache entry was created.'); 57 58 // Create a new instance of the class. Confirm that both the offset 59 // requested previously, and one that has not yet been requested are both 60 // available. 61 $registry = new ThemeRegistry($cid, $cache, $lock_backend, ['theme_registry'], $this->container->get('module_handler')->isLoaded()); 62 $this->assertNotEmpty($registry->get('theme_test_template_test'), 'Offset was returned correctly from the theme registry'); 63 $this->assertNotEmpty($registry->get('theme_test_template_test_2'), 'Offset was returned correctly from the theme registry'); 64 } 65 66 /** 67 * Tests the theme registry with multiple subthemes. 68 */ 69 public function testMultipleSubThemes() { 70 $theme_handler = \Drupal::service('theme_handler'); 71 \Drupal::service('theme_installer')->install(['test_basetheme', 'test_subtheme', 'test_subsubtheme']); 72 73 $registry_subsub_theme = new Registry($this->root, \Drupal::cache(), \Drupal::lock(), \Drupal::moduleHandler(), $theme_handler, \Drupal::service('theme.initialization'), 'test_subsubtheme'); 74 $registry_subsub_theme->setThemeManager(\Drupal::theme()); 75 $registry_sub_theme = new Registry($this->root, \Drupal::cache(), \Drupal::lock(), \Drupal::moduleHandler(), $theme_handler, \Drupal::service('theme.initialization'), 'test_subtheme'); 76 $registry_sub_theme->setThemeManager(\Drupal::theme()); 77 $registry_base_theme = new Registry($this->root, \Drupal::cache(), \Drupal::lock(), \Drupal::moduleHandler(), $theme_handler, \Drupal::service('theme.initialization'), 'test_basetheme'); 78 $registry_base_theme->setThemeManager(\Drupal::theme()); 79 80 $preprocess_functions = $registry_subsub_theme->get()['theme_test_template_test']['preprocess functions']; 81 $this->assertSame([ 82 'template_preprocess', 83 'test_basetheme_preprocess_theme_test_template_test', 84 'test_subtheme_preprocess_theme_test_template_test', 85 'test_subsubtheme_preprocess_theme_test_template_test', 86 ], $preprocess_functions); 87 88 $preprocess_functions = $registry_sub_theme->get()['theme_test_template_test']['preprocess functions']; 89 $this->assertSame([ 90 'template_preprocess', 91 'test_basetheme_preprocess_theme_test_template_test', 92 'test_subtheme_preprocess_theme_test_template_test', 93 ], $preprocess_functions); 94 95 $preprocess_functions = $registry_base_theme->get()['theme_test_template_test']['preprocess functions']; 96 $this->assertSame([ 97 'template_preprocess', 98 'test_basetheme_preprocess_theme_test_template_test', 99 ], $preprocess_functions); 100 } 101 102 /** 103 * Tests the theme registry with suggestions. 104 */ 105 public function testSuggestionPreprocessFunctions() { 106 $theme_handler = \Drupal::service('theme_handler'); 107 \Drupal::service('theme_installer')->install(['test_theme']); 108 109 $registry_theme = new Registry($this->root, \Drupal::cache(), \Drupal::lock(), \Drupal::moduleHandler(), $theme_handler, \Drupal::service('theme.initialization'), 'test_theme'); 110 $registry_theme->setThemeManager(\Drupal::theme()); 111 112 $suggestions = ['__kitten', '__flamingo']; 113 $expected_preprocess_functions = [ 114 'template_preprocess', 115 'theme_test_preprocess_theme_test_preprocess_suggestions', 116 ]; 117 $suggestion = ''; 118 $hook = 'theme_test_preprocess_suggestions'; 119 do { 120 $hook .= "$suggestion"; 121 $expected_preprocess_functions[] = "test_theme_preprocess_$hook"; 122 $preprocess_functions = $registry_theme->get()[$hook]['preprocess functions']; 123 $this->assertSame($expected_preprocess_functions, $preprocess_functions, "$hook has correct preprocess functions."); 124 } while ($suggestion = array_shift($suggestions)); 125 126 $expected_preprocess_functions = [ 127 'template_preprocess', 128 'theme_test_preprocess_theme_test_preprocess_suggestions', 129 'test_theme_preprocess_theme_test_preprocess_suggestions', 130 'test_theme_preprocess_theme_test_preprocess_suggestions__kitten', 131 ]; 132 133 $preprocess_functions = $registry_theme->get()['theme_test_preprocess_suggestions__kitten__bearcat']['preprocess functions']; 134 $this->assertSame($expected_preprocess_functions, $preprocess_functions, 'Suggestion implemented as a template correctly inherits preprocess functions.'); 135 136 $this->assertTrue(isset($registry_theme->get()['theme_test_preprocess_suggestions__kitten__meerkat__tarsier__moose']), 'Preprocess function with an unimplemented lower-level suggestion is added to the registry.'); 137 } 138 139 /** 140 * Tests that the theme registry can be altered by themes. 141 */ 142 public function testThemeRegistryAlterByTheme() { 143 144 /** @var \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler */ 145 $theme_handler = \Drupal::service('theme_handler'); 146 \Drupal::service('theme_installer')->install(['test_theme']); 147 $this->config('system.theme')->set('default', 'test_theme')->save(); 148 149 $registry = new Registry($this->root, \Drupal::cache(), \Drupal::lock(), \Drupal::moduleHandler(), $theme_handler, \Drupal::service('theme.initialization'), 'test_theme'); 150 $registry->setThemeManager(\Drupal::theme()); 151 $this->assertEqual('value', $registry->get()['theme_test_template_test']['variables']['additional']); 152 } 153 154 /** 155 * Tests front node theme suggestion generation. 156 */ 157 public function testThemeSuggestions() { 158 // Mock the current page as the front page. 159 /** @var \Drupal\Core\Path\PathMatcherInterface $path_matcher */ 160 $path_matcher = $this->prophesize(PathMatcherInterface::class); 161 $path_matcher->isFrontPage()->willReturn(TRUE); 162 $this->container->set('path.matcher', $path_matcher->reveal()); 163 /** @var \Drupal\Core\Path\CurrentPathStack $path_matcher */ 164 $path_current = $this->prophesize(CurrentPathStack::class); 165 $path_current->getPath()->willReturn('/node/1'); 166 $this->container->set('path.current', $path_current->reveal()); 167 168 // Check suggestions provided through hook_theme_suggestions_html(). 169 $suggestions = \Drupal::moduleHandler()->invokeAll('theme_suggestions_html', [[]]); 170 $this->assertSame([ 171 'html__node', 172 'html__node__%', 173 'html__node__1', 174 'html__front', 175 ], $suggestions, 'Found expected html node suggestions.'); 176 177 // Check suggestions provided through hook_theme_suggestions_page(). 178 $suggestions = \Drupal::moduleHandler()->invokeAll('theme_suggestions_page', [[]]); 179 $this->assertSame([ 180 'page__node', 181 'page__node__%', 182 'page__node__1', 183 'page__front', 184 ], $suggestions, 'Found expected page node suggestions.'); 185 } 186 187 /** 188 * Data provider for test40xThemeSuggestions(). 189 * 190 * @return array 191 * An associative array of 40x theme suggestions. 192 */ 193 public function provider40xThemeSuggestions() { 194 return [ 195 ['system.401', 'page__401'], 196 ['system.403', 'page__403'], 197 ['system.404', 'page__404'], 198 ]; 199 } 200 201 /** 202 * Tests page theme suggestions for 40x responses. 203 * 204 * @dataProvider provider40xThemeSuggestions 205 */ 206 public function test40xThemeSuggestions($route, $suggestion) { 207 /** @var \Drupal\Core\Path\PathMatcherInterface $path_matcher */ 208 $path_matcher = $this->prophesize(PathMatcherInterface::class); 209 $path_matcher->isFrontPage()->willReturn(FALSE); 210 \Drupal::getContainer()->set('path.matcher', $path_matcher->reveal()); 211 /** @var \Drupal\Core\Path\CurrentPathStack $path_current */ 212 $path_current = $this->prophesize(CurrentPathStack::class); 213 $path_current->getPath()->willReturn('/node/123'); 214 \Drupal::getContainer()->set('path.current', $path_current->reveal()); 215 /** @var \Drupal\Core\Routing\RouteMatchInterface $route_matcher */ 216 $route_matcher = $this->prophesize(RouteMatchInterface::class); 217 $route_matcher->getRouteName()->willReturn($route); 218 \Drupal::getContainer()->set('current_route_match', $route_matcher->reveal()); 219 220 $suggestions = \Drupal::moduleHandler()->invokeAll('theme_suggestions_page', [[]]); 221 $this->assertSame([ 222 'page__node', 223 'page__node__%', 224 'page__node__123', 225 'page__4xx', 226 $suggestion, 227 ], $suggestions); 228 } 229 230 /** 231 * Tests theme-provided templates that are registered by modules. 232 */ 233 public function testThemeTemplatesRegisteredByModules() { 234 $theme_handler = \Drupal::service('theme_handler'); 235 \Drupal::service('theme_installer')->install(['test_theme']); 236 237 $registry_theme = new Registry($this->root, \Drupal::cache(), \Drupal::lock(), \Drupal::moduleHandler(), $theme_handler, \Drupal::service('theme.initialization'), 'test_theme'); 238 $registry_theme->setThemeManager(\Drupal::theme()); 239 240 $expected = [ 241 'template_preprocess', 242 'template_preprocess_container', 243 'template_preprocess_theme_test_registered_by_module', 244 ]; 245 $registry = $registry_theme->get(); 246 $this->assertEquals($expected, array_values($registry['theme_test_registered_by_module']['preprocess functions'])); 247 } 248 249} 250