More updates to stop using dev or alpha or beta versions.
[yaffs-website] / web / core / tests / Drupal / Tests / Core / Theme / RegistryTest.php
1 <?php
2
3 /**
4  * @file
5  * Contains \Drupal\Tests\Core\Theme\RegistryTest.
6  */
7
8 namespace Drupal\Tests\Core\Theme;
9
10 use Drupal\Core\Theme\ActiveTheme;
11 use Drupal\Core\Theme\Registry;
12 use Drupal\Tests\UnitTestCase;
13
14 /**
15  * @coversDefaultClass \Drupal\Core\Theme\Registry
16  * @group Theme
17  */
18 class RegistryTest extends UnitTestCase {
19
20   /**
21    * The tested theme registry.
22    *
23    * @var \Drupal\Tests\Core\Theme\TestRegistry
24    */
25   protected $registry;
26
27   /**
28    * The mocked cache backend.
29    *
30    * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject
31    */
32   protected $cache;
33
34   /**
35    * The mocked lock backend.
36    *
37    * @var \Drupal\Core\Lock\LockBackendInterface|\PHPUnit_Framework_MockObject_MockObject
38    */
39   protected $lock;
40
41   /**
42    * The mocked module handler.
43    *
44    * @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
45    */
46   protected $moduleHandler;
47
48   /**
49    * The mocked theme handler.
50    *
51    * @var \Drupal\Core\Extension\ThemeHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
52    */
53   protected $themeHandler;
54
55   /**
56    * The mocked theme initialization.
57    *
58    * @var \Drupal\Core\Theme\ThemeInitializationInterface|\PHPUnit_Framework_MockObject_MockObject
59    */
60   protected $themeInitialization;
61
62   /**
63    * The theme manager.
64    *
65    * @var \Drupal\Core\Theme\ThemeManagerInterface|\PHPUnit_Framework_MockObject_MockObject
66    */
67   protected $themeManager;
68
69   /**
70    * The list of functions that get_defined_functions() should provide.
71    *
72    * @var array
73    */
74   public static $functions = [];
75
76   /**
77    * {@inheritdoc}
78    */
79   protected function setUp() {
80     parent::setUp();
81
82     $this->cache = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
83     $this->lock = $this->getMock('Drupal\Core\Lock\LockBackendInterface');
84     $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
85     $this->themeHandler = $this->getMock('Drupal\Core\Extension\ThemeHandlerInterface');
86     $this->themeInitialization = $this->getMock('Drupal\Core\Theme\ThemeInitializationInterface');
87     $this->themeManager = $this->getMock('Drupal\Core\Theme\ThemeManagerInterface');
88
89     $this->setupTheme();
90   }
91
92   /**
93    * {@inheritdoc}
94    */
95   protected function tearDown() {
96     parent::tearDown();
97     static::$functions = [];
98   }
99
100   /**
101    * Tests getting the theme registry defined by a module.
102    */
103   public function testGetRegistryForModule() {
104     $test_theme = new ActiveTheme([
105       'name' => 'test_theme',
106       'path' => 'core/modules/system/tests/themes/test_theme/test_theme.info.yml',
107       'engine' => 'twig',
108       'owner' => 'twig',
109       'stylesheets_remove' => [],
110       'libraries_override' => [],
111       'libraries_extend' => [],
112       'libraries' => [],
113       'extension' => '.twig',
114       'base_themes' => [],
115     ]);
116
117     $test_stable = new ActiveTheme([
118       'name' => 'test_stable',
119       'path' => 'core/modules/system/tests/themes/test_stable/test_stable.info.yml',
120       'engine' => 'twig',
121       'owner' => 'twig',
122       'stylesheets_remove' => [],
123       'libraries_override' => [],
124       'libraries_extend' => [],
125       'libraries' => [],
126       'extension' => '.twig',
127       'base_themes' => [],
128     ]);
129
130     $this->themeManager->expects($this->exactly(2))
131       ->method('getActiveTheme')
132       ->willReturnOnConsecutiveCalls($test_theme, $test_stable);
133
134     // Include the module and theme files so that hook_theme can be called.
135     include_once $this->root . '/core/modules/system/tests/modules/theme_test/theme_test.module';
136     include_once $this->root . '/core/modules/system/tests/themes/test_stable/test_stable.theme';
137     $this->moduleHandler->expects($this->exactly(2))
138       ->method('getImplementations')
139       ->with('theme')
140       ->will($this->returnValue(['theme_test']));
141     $this->moduleHandler->expects($this->atLeastOnce())
142       ->method('getModuleList')
143       ->willReturn([]);
144
145     $registry = $this->registry->get();
146
147     // Ensure that the registry entries from the module are found.
148     $this->assertArrayHasKey('theme_test', $registry);
149     $this->assertArrayHasKey('theme_test_template_test', $registry);
150     $this->assertArrayHasKey('theme_test_template_test_2', $registry);
151     $this->assertArrayHasKey('theme_test_suggestion_provided', $registry);
152     $this->assertArrayHasKey('theme_test_specific_suggestions', $registry);
153     $this->assertArrayHasKey('theme_test_suggestions', $registry);
154     $this->assertArrayHasKey('theme_test_function_suggestions', $registry);
155     $this->assertArrayHasKey('theme_test_foo', $registry);
156     $this->assertArrayHasKey('theme_test_render_element', $registry);
157     $this->assertArrayHasKey('theme_test_render_element_children', $registry);
158     $this->assertArrayHasKey('theme_test_function_template_override', $registry);
159
160     $this->assertArrayNotHasKey('test_theme_not_existing_function', $registry);
161     $this->assertFalse(in_array('test_stable_preprocess_theme_test_render_element', $registry['theme_test_render_element']['preprocess functions']));
162
163     $info = $registry['theme_test_function_suggestions'];
164     $this->assertEquals('module', $info['type']);
165     $this->assertEquals('core/modules/system/tests/modules/theme_test', $info['theme path']);
166     $this->assertEquals('theme_theme_test_function_suggestions', $info['function']);
167     $this->assertEquals([], $info['variables']);
168
169     // The second call will initialize with the second theme. Ensure that this
170     // returns a different object and the discovery for the second theme's
171     // preprocess function worked.
172     $other_registry = $this->registry->get();
173     $this->assertNotSame($registry, $other_registry);
174     $this->assertTrue(in_array('test_stable_preprocess_theme_test_render_element', $other_registry['theme_test_render_element']['preprocess functions']));
175   }
176
177   /**
178    * @covers ::postProcessExtension
179    * @covers ::completeSuggestion
180    * @covers ::mergePreprocessFunctions
181    *
182    * @dataProvider providerTestPostProcessExtension
183    *
184    * @param array $defined_functions
185    *   An array of functions to be used in place of get_defined_functions().
186    * @param array $hooks
187    *   An array of theme hooks to process.
188    * @param array $expected
189    *   The expected results.
190    */
191   public function testPostProcessExtension($defined_functions, $hooks, $expected) {
192     static::$functions['user'] = $defined_functions;
193
194     $theme = $this->prophesize(ActiveTheme::class);
195     $theme->getBaseThemes()->willReturn([]);
196     $theme->getName()->willReturn('test');
197     $theme->getEngine()->willReturn('twig');
198
199     $this->moduleHandler->expects($this->atLeastOnce())
200       ->method('getModuleList')
201       ->willReturn([]);
202
203     $class = new \ReflectionClass(TestRegistry::class);
204     $reflection_method = $class->getMethod('postProcessExtension');
205     $reflection_method->setAccessible(TRUE);
206     $reflection_method->invokeArgs($this->registry, [&$hooks, $theme->reveal()]);
207
208     $this->assertArrayEquals($expected, $hooks);
209   }
210
211   /**
212    * Provides test data to ::testPostProcessExtension().
213    */
214   public function providerTestPostProcessExtension() {
215     // This is test data for unit testing
216     // \Drupal\Core\Theme\Registry::postProcessExtension(), not what happens
217     // before it. Therefore, for all test data:
218     // - Explicitly defined hooks also come with explicitly defined preprocess
219     //   functions, because those are collected in
220     //   \Drupal\Core\Theme\Registry::processExtension().
221     // - Explicitly defined hooks that set a 'base hook' also have
222     //   'incomplete preprocess functions' set to TRUE, since that is done in
223     //   \Drupal\Core\Theme\Registry::processExtension().
224     $data = [];
225
226     // Test the discovery of suggestions via the presence of preprocess
227     // functions that follow the "__" naming pattern.
228     $data['base_hook_with_autodiscovered_suggestions'] = [
229       'defined_functions' => [
230         'test_preprocess_test_hook__suggestion',
231         'test_preprocess_test_hook__suggestion__another',
232       ],
233       'hooks' => [
234         'test_hook' => [
235           'preprocess functions' => ['explicit_preprocess_test_hook'],
236         ],
237       ],
238       'expected' => [
239         'test_hook' => [
240           'preprocess functions' => [
241             'explicit_preprocess_test_hook',
242           ],
243         ],
244         'test_hook__suggestion' => [
245           'preprocess functions' => [
246             'explicit_preprocess_test_hook',
247             'test_preprocess_test_hook__suggestion',
248           ],
249           'base hook' => 'test_hook',
250         ],
251         'test_hook__suggestion__another' => [
252           'preprocess functions' => [
253             'explicit_preprocess_test_hook',
254             'test_preprocess_test_hook__suggestion',
255             'test_preprocess_test_hook__suggestion__another',
256           ],
257           'base hook' => 'test_hook',
258         ],
259       ],
260     ];
261
262     // Test that suggestions following the "__" naming pattern can also be
263     // explicitly defined in hook_theme(), such as 'field__node__title' defined
264     // in node_theme().
265     $data['base_hook_with_explicit_suggestions'] = [
266       'defined_functions' => [],
267       'hooks' => [
268         'test_hook' => [
269           'preprocess functions' => ['explicit_preprocess_test_hook'],
270         ],
271         'test_hook__suggestion__another' => [
272           'base hook' => 'test_hook',
273           'preprocess functions' => ['explicit_preprocess_test_hook__suggestion__another'],
274           'incomplete preprocess functions' => TRUE,
275         ],
276       ],
277       'expected' => [
278         'test_hook' => [
279           'preprocess functions' => [
280             'explicit_preprocess_test_hook',
281           ],
282         ],
283         'test_hook__suggestion__another' => [
284           'preprocess functions' => [
285             'explicit_preprocess_test_hook',
286             'explicit_preprocess_test_hook__suggestion__another',
287           ],
288           'base hook' => 'test_hook',
289         ],
290       ],
291     ];
292
293     // Same as above, but also test that a preprocess function for an
294     // intermediary suggestion level gets discovered.
295     $data['base_hook_with_explicit_suggestions_and_intermediary_preprocess_function'] = [
296       'defined_functions' => [
297         'test_preprocess_test_hook__suggestion',
298       ],
299       'hooks' => [
300         'test_hook' => [
301           'preprocess functions' => ['explicit_preprocess_test_hook'],
302         ],
303         'test_hook__suggestion__another' => [
304           'base hook' => 'test_hook',
305           'preprocess functions' => ['explicit_preprocess_test_hook__suggestion__another'],
306           'incomplete preprocess functions' => TRUE,
307         ],
308       ],
309       'expected' => [
310         'test_hook' => [
311           'preprocess functions' => [
312             'explicit_preprocess_test_hook',
313           ],
314         ],
315         'test_hook__suggestion' => [
316           'preprocess functions' => [
317             'explicit_preprocess_test_hook',
318             'test_preprocess_test_hook__suggestion',
319           ],
320           'base hook' => 'test_hook',
321         ],
322         'test_hook__suggestion__another' => [
323           'preprocess functions' => [
324             'explicit_preprocess_test_hook',
325             'test_preprocess_test_hook__suggestion',
326             'explicit_preprocess_test_hook__suggestion__another',
327           ],
328           'base hook' => 'test_hook',
329         ],
330       ],
331     ];
332
333     // Test that hooks not following the "__" naming pattern can explicitly
334     // specify a base hook, such as is done in
335     // \Drupal\Core\Layout\LayoutPluginManager::getThemeImplementations().
336     $data['child_hook_without_magic_naming'] = [
337       'defined_functions' => [],
338       'hooks' => [
339         'test_hook' => [
340           'preprocess functions' => ['explicit_preprocess_test_hook'],
341         ],
342         'child_hook' => [
343           'base hook' => 'test_hook',
344           'preprocess functions' => ['explicit_preprocess_child_hook'],
345           'incomplete preprocess functions' => TRUE,
346         ],
347       ],
348       'expected' => [
349         'test_hook' => [
350           'preprocess functions' => [
351             'explicit_preprocess_test_hook',
352           ],
353         ],
354         'child_hook' => [
355           'preprocess functions' => [
356             'explicit_preprocess_test_hook',
357             'explicit_preprocess_child_hook',
358           ],
359           'base hook' => 'test_hook',
360         ],
361       ],
362     ];
363
364     // Same as above, but also test that such child hooks can also be extended
365     // with magically named suggestions.
366     $data['child_hook_with_suggestions'] = [
367       'defined_functions' => [
368         'test_preprocess_child_hook__suggestion',
369         'test_preprocess_child_hook__suggestion__another',
370       ],
371       'hooks' => [
372         'test_hook' => [
373           'preprocess functions' => ['explicit_preprocess_test_hook'],
374         ],
375         'child_hook' => [
376           'base hook' => 'test_hook',
377           'preprocess functions' => ['explicit_preprocess_child_hook'],
378           'incomplete preprocess functions' => TRUE,
379         ],
380       ],
381       'expected' => [
382         'test_hook' => [
383           'preprocess functions' => [
384             'explicit_preprocess_test_hook',
385           ],
386         ],
387         'child_hook' => [
388           'preprocess functions' => [
389             'explicit_preprocess_test_hook',
390             'explicit_preprocess_child_hook',
391           ],
392           'base hook' => 'test_hook',
393         ],
394         'child_hook__suggestion' => [
395           'preprocess functions' => [
396             'explicit_preprocess_test_hook',
397             'explicit_preprocess_child_hook',
398             'test_preprocess_child_hook__suggestion',
399           ],
400           'base hook' => 'test_hook',
401         ],
402         'child_hook__suggestion__another' => [
403           'preprocess functions' => [
404             'explicit_preprocess_test_hook',
405             'explicit_preprocess_child_hook',
406             'test_preprocess_child_hook__suggestion',
407             'test_preprocess_child_hook__suggestion__another',
408           ],
409           'base hook' => 'test_hook',
410         ],
411       ],
412     ];
413
414     // Test that a suggestion following the "__" naming pattern can specify a
415     // different base hook than what is implied by that pattern. Ensure that
416     // preprocess functions from both the naming pattern and from 'base hook'
417     // are collected.
418     $data['suggestion_with_alternate_base_hook'] = [
419       'defined_functions' => [
420         'test_preprocess_test_hook__suggestion',
421       ],
422       'hooks' => [
423         'test_hook' => [
424           'preprocess functions' => ['explicit_preprocess_test_hook'],
425         ],
426         'alternate_base_hook' => [
427           'preprocess functions' => ['explicit_preprocess_alternate_base_hook'],
428         ],
429         'test_hook__suggestion__another' => [
430           'base hook' => 'alternate_base_hook',
431           'preprocess functions' => ['explicit_preprocess_test_hook__suggestion__another'],
432           'incomplete preprocess functions' => TRUE,
433         ],
434       ],
435       'expected' => [
436         'test_hook' => [
437           'preprocess functions' => [
438             'explicit_preprocess_test_hook',
439           ],
440         ],
441         'alternate_base_hook' => [
442           'preprocess functions' => [
443             'explicit_preprocess_alternate_base_hook',
444           ],
445         ],
446         'test_hook__suggestion' => [
447           'preprocess functions' => [
448             'explicit_preprocess_test_hook',
449             'test_preprocess_test_hook__suggestion',
450           ],
451           'base hook' => 'test_hook',
452         ],
453         'test_hook__suggestion__another' => [
454           'preprocess functions' => [
455             'explicit_preprocess_alternate_base_hook',
456             'explicit_preprocess_test_hook',
457             'test_preprocess_test_hook__suggestion',
458             'explicit_preprocess_test_hook__suggestion__another',
459           ],
460           'base hook' => 'alternate_base_hook',
461         ],
462       ],
463     ];
464
465     // Test when a base hook is missing.
466     $data['missing_base_hook'] = [
467       'defined_functions' => [],
468       'hooks' => [
469         'child_hook' => [
470           'base hook' => 'test_hook',
471           'preprocess functions' => ['explicit_preprocess_child_hook'],
472           'incomplete preprocess functions' => TRUE,
473         ],
474       ],
475       'expected' => [
476         'child_hook' => [
477           'preprocess functions' => [
478             'explicit_preprocess_child_hook',
479           ],
480           'base hook' => 'test_hook',
481         ],
482       ],
483     ];
484
485     return $data;
486   }
487
488   protected function setupTheme() {
489     $this->registry = new TestRegistry($this->root, $this->cache, $this->lock, $this->moduleHandler, $this->themeHandler, $this->themeInitialization);
490     $this->registry->setThemeManager($this->themeManager);
491   }
492
493 }
494
495 class TestRegistry extends Registry {
496
497   protected function getPath($module) {
498     if ($module == 'theme_test') {
499       return 'core/modules/system/tests/modules/theme_test';
500     }
501   }
502
503 }
504
505 namespace Drupal\Core\Theme;
506
507 use Drupal\Tests\Core\Theme\RegistryTest;
508
509 /**
510  * Overrides get_defined_functions() with a configurable mock.
511  */
512 function get_defined_functions() {
513   return RegistryTest::$functions ?: \get_defined_functions();
514 }