Updated to Drupal 8.5. Core Media not yet in use.
[yaffs-website] / web / core / tests / Drupal / Tests / Core / Plugin / ContextHandlerTest.php
1 <?php
2
3 /**
4  * @file
5  * Contains \Drupal\Tests\Core\Plugin\ContextHandlerTest.
6  */
7
8 namespace Drupal\Tests\Core\Plugin;
9
10 use Drupal\Component\Plugin\ConfigurablePluginInterface;
11 use Drupal\Component\Plugin\Exception\ContextException;
12 use Drupal\Core\Cache\NullBackend;
13 use Drupal\Core\DependencyInjection\ClassResolverInterface;
14 use Drupal\Core\DependencyInjection\ContainerBuilder;
15 use Drupal\Core\Extension\ModuleHandlerInterface;
16 use Drupal\Core\Plugin\Context\ContextDefinition;
17 use Drupal\Core\Plugin\Context\ContextHandler;
18 use Drupal\Core\Plugin\ContextAwarePluginInterface;
19 use Drupal\Core\TypedData\DataDefinition;
20 use Drupal\Core\TypedData\Plugin\DataType\StringData;
21 use Drupal\Core\TypedData\TypedDataManager;
22 use Drupal\Core\Validation\ConstraintManager;
23 use Drupal\Tests\UnitTestCase;
24 use Prophecy\Argument;
25
26 /**
27  * @coversDefaultClass \Drupal\Core\Plugin\Context\ContextHandler
28  * @group Plugin
29  */
30 class ContextHandlerTest extends UnitTestCase {
31
32   /**
33    * The context handler.
34    *
35    * @var \Drupal\Core\Plugin\Context\ContextHandler
36    */
37   protected $contextHandler;
38
39   /**
40    * {@inheritdoc}
41    */
42   protected function setUp() {
43     parent::setUp();
44
45     $this->contextHandler = new ContextHandler();
46
47     $namespaces = new \ArrayObject([
48       'Drupal\\Core\\TypedData' => $this->root . '/core/lib/Drupal/Core/TypedData',
49       'Drupal\\Core\\Validation' => $this->root . '/core/lib/Drupal/Core/Validation',
50     ]);
51     $cache_backend = new NullBackend('cache');
52     $module_handler = $this->prophesize(ModuleHandlerInterface::class);
53     $class_resolver = $this->prophesize(ClassResolverInterface::class);
54     $class_resolver->getInstanceFromDefinition(Argument::type('string'))->will(function ($arguments) {
55       $class_name = $arguments[0];
56       return new $class_name();
57     });
58     $type_data_manager = new TypedDataManager($namespaces, $cache_backend, $module_handler->reveal(), $class_resolver->reveal());
59     $type_data_manager->setValidationConstraintManager(
60       new ConstraintManager($namespaces, $cache_backend, $module_handler->reveal())
61     );
62
63     $container = new ContainerBuilder();
64     $container->set('typed_data_manager', $type_data_manager);
65     \Drupal::setContainer($container);
66   }
67
68   /**
69    * @covers ::checkRequirements
70    *
71    * @dataProvider providerTestCheckRequirements
72    */
73   public function testCheckRequirements($contexts, $requirements, $expected) {
74     $this->assertSame($expected, $this->contextHandler->checkRequirements($contexts, $requirements));
75   }
76
77   /**
78    * Provides data for testCheckRequirements().
79    */
80   public function providerTestCheckRequirements() {
81     $requirement_optional = new ContextDefinition();
82     $requirement_optional->setRequired(FALSE);
83
84     $requirement_any = new ContextDefinition();
85     $requirement_any->setRequired(TRUE);
86
87     $context_any = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
88     $context_any->expects($this->atLeastOnce())
89       ->method('getContextDefinition')
90       ->will($this->returnValue(new ContextDefinition('any')));
91
92     $requirement_specific = new ContextDefinition('string');
93     $requirement_specific->setConstraints(['Blank' => []]);
94
95     $context_constraint_mismatch = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
96     $context_constraint_mismatch->expects($this->atLeastOnce())
97       ->method('getContextDefinition')
98       ->will($this->returnValue(new ContextDefinition('foo')));
99     $context_datatype_mismatch = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
100     $context_datatype_mismatch->expects($this->atLeastOnce())
101       ->method('getContextDefinition')
102       ->will($this->returnValue(new ContextDefinition('fuzzy')));
103
104     $context_definition_specific = new ContextDefinition('string');
105     $context_definition_specific->setConstraints(['Blank' => []]);
106     $context_specific = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
107     $context_specific->expects($this->atLeastOnce())
108       ->method('getContextDefinition')
109       ->will($this->returnValue($context_definition_specific));
110
111     $data = [];
112     $data[] = [[], [], TRUE];
113     $data[] = [[], [$requirement_any], FALSE];
114     $data[] = [[], [$requirement_optional], TRUE];
115     $data[] = [[], [$requirement_any, $requirement_optional], FALSE];
116     $data[] = [[$context_any], [$requirement_any], TRUE];
117     $data[] = [[$context_constraint_mismatch], [$requirement_specific], FALSE];
118     $data[] = [[$context_datatype_mismatch], [$requirement_specific], FALSE];
119     $data[] = [[$context_specific], [$requirement_specific], TRUE];
120
121     return $data;
122   }
123
124   /**
125    * @covers ::getMatchingContexts
126    *
127    * @dataProvider providerTestGetMatchingContexts
128    */
129   public function testGetMatchingContexts($contexts, $requirement, $expected = NULL) {
130     if (is_null($expected)) {
131       $expected = $contexts;
132     }
133     $this->assertSame($expected, $this->contextHandler->getMatchingContexts($contexts, $requirement));
134   }
135
136   /**
137    * Provides data for testGetMatchingContexts().
138    */
139   public function providerTestGetMatchingContexts() {
140     $requirement_any = new ContextDefinition();
141
142     $requirement_specific = new ContextDefinition('string');
143     $requirement_specific->setConstraints(['Blank' => []]);
144
145     $context_any = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
146     $context_any->expects($this->atLeastOnce())
147       ->method('getContextDefinition')
148       ->will($this->returnValue(new ContextDefinition('any')));
149     $context_constraint_mismatch = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
150     $context_constraint_mismatch->expects($this->atLeastOnce())
151       ->method('getContextDefinition')
152       ->will($this->returnValue(new ContextDefinition('foo')));
153     $context_datatype_mismatch = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
154     $context_datatype_mismatch->expects($this->atLeastOnce())
155       ->method('getContextDefinition')
156       ->will($this->returnValue(new ContextDefinition('fuzzy')));
157     $context_definition_specific = new ContextDefinition('string');
158     $context_definition_specific->setConstraints(['Blank' => []]);
159     $context_specific = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
160     $context_specific->expects($this->atLeastOnce())
161       ->method('getContextDefinition')
162       ->will($this->returnValue($context_definition_specific));
163
164     $data = [];
165     // No context will return no valid contexts.
166     $data[] = [[], $requirement_any];
167     // A context with a generic matching requirement is valid.
168     $data[] = [[$context_any], $requirement_any];
169     // A context with a specific matching requirement is valid.
170     $data[] = [[$context_specific], $requirement_specific];
171
172     // A context with a mismatched constraint is invalid.
173     $data[] = [[$context_constraint_mismatch], $requirement_specific, []];
174     // A context with a mismatched datatype is invalid.
175     $data[] = [[$context_datatype_mismatch], $requirement_specific, []];
176
177     return $data;
178   }
179
180   /**
181    * @covers ::filterPluginDefinitionsByContexts
182    *
183    * @dataProvider providerTestFilterPluginDefinitionsByContexts
184    */
185   public function testFilterPluginDefinitionsByContexts($has_context, $definitions, $expected) {
186     if ($has_context) {
187       $context = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
188       $expected_context_definition = (new ContextDefinition('string'))->setConstraints(['Blank' => []]);
189       $context->expects($this->atLeastOnce())
190         ->method('getContextDefinition')
191         ->will($this->returnValue($expected_context_definition));
192       $contexts = [$context];
193     }
194     else {
195       $contexts = [];
196     }
197
198     $this->assertSame($expected, $this->contextHandler->filterPluginDefinitionsByContexts($contexts, $definitions));
199   }
200
201   /**
202    * Provides data for testFilterPluginDefinitionsByContexts().
203    */
204   public function providerTestFilterPluginDefinitionsByContexts() {
205     $data = [];
206
207     $plugins = [];
208     // No context and no plugins, no plugins available.
209     $data[] = [FALSE, $plugins, []];
210
211     $plugins = ['expected_plugin' => []];
212     // No context, all plugins available.
213     $data[] = [FALSE, $plugins, $plugins];
214
215     $plugins = ['expected_plugin' => ['context' => []]];
216     // No context, all plugins available.
217     $data[] = [FALSE, $plugins, $plugins];
218
219     $plugins = ['expected_plugin' => ['context' => ['context1' => new ContextDefinition('string')]]];
220     // Missing context, no plugins available.
221     $data[] = [FALSE, $plugins, []];
222     // Satisfied context, all plugins available.
223     $data[] = [TRUE, $plugins, $plugins];
224
225     $mismatched_context_definition = (new ContextDefinition('expected_data_type'))->setConstraints(['mismatched_constraint_name' => 'mismatched_constraint_value']);
226     $plugins = ['expected_plugin' => ['context' => ['context1' => $mismatched_context_definition]]];
227     // Mismatched constraints, no plugins available.
228     $data[] = [TRUE, $plugins, []];
229
230     $optional_mismatched_context_definition = clone $mismatched_context_definition;
231     $optional_mismatched_context_definition->setRequired(FALSE);
232     $plugins = ['expected_plugin' => ['context' => ['context1' => $optional_mismatched_context_definition]]];
233     // Optional mismatched constraint, all plugins available.
234     $data[] = [FALSE, $plugins, $plugins];
235
236     $expected_context_definition = (new ContextDefinition('string'))->setConstraints(['Blank' => []]);
237     $plugins = ['expected_plugin' => ['context' => ['context1' => $expected_context_definition]]];
238     // Satisfied context with constraint, all plugins available.
239     $data[] = [TRUE, $plugins, $plugins];
240
241     $optional_expected_context_definition = clone $expected_context_definition;
242     $optional_expected_context_definition->setRequired(FALSE);
243     $plugins = ['expected_plugin' => ['context' => ['context1' => $optional_expected_context_definition]]];
244     // Optional unsatisfied context, all plugins available.
245     $data[] = [FALSE, $plugins, $plugins];
246
247     $unexpected_context_definition = (new ContextDefinition('unexpected_data_type'))->setConstraints(['mismatched_constraint_name' => 'mismatched_constraint_value']);
248     $plugins = [
249       'unexpected_plugin' => ['context' => ['context1' => $unexpected_context_definition]],
250       'expected_plugin' => ['context' => ['context2' => new ContextDefinition('string')]],
251     ];
252     // Context only satisfies one plugin.
253     $data[] = [TRUE, $plugins, ['expected_plugin' => $plugins['expected_plugin']]];
254
255     return $data;
256   }
257
258   /**
259    * @covers ::applyContextMapping
260    */
261   public function testApplyContextMapping() {
262     $context_hit_data = StringData::createInstance(DataDefinition::create('string'));
263     $context_hit_data->setValue('foo');
264     $context_hit = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
265     $context_hit->expects($this->atLeastOnce())
266       ->method('getContextData')
267       ->will($this->returnValue($context_hit_data));
268     $context_miss_data = StringData::createInstance(DataDefinition::create('string'));
269     $context_miss_data->setValue('bar');
270     $context_hit->expects($this->atLeastOnce())
271       ->method('hasContextValue')
272       ->willReturn(TRUE);
273     $context_miss = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
274     $context_miss->expects($this->never())
275       ->method('getContextData');
276
277     $contexts = [
278       'hit' => $context_hit,
279       'miss' => $context_miss,
280     ];
281
282     $context_definition = $this->getMock('Drupal\Core\Plugin\Context\ContextDefinitionInterface');
283
284     $plugin = $this->getMock('Drupal\Core\Plugin\ContextAwarePluginInterface');
285     $plugin->expects($this->once())
286       ->method('getContextMapping')
287       ->willReturn([]);
288     $plugin->expects($this->once())
289       ->method('getContextDefinitions')
290       ->will($this->returnValue(['hit' => $context_definition]));
291     $plugin->expects($this->once())
292       ->method('setContextValue')
293       ->with('hit', $context_hit_data);
294
295     // Make sure that the cacheability metadata is passed to the plugin context.
296     $plugin_context = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
297     $plugin_context->expects($this->once())
298       ->method('addCacheableDependency')
299       ->with($context_hit);
300     $plugin->expects($this->once())
301       ->method('getContext')
302       ->with('hit')
303       ->willReturn($plugin_context);
304
305     $this->contextHandler->applyContextMapping($plugin, $contexts);
306   }
307
308   /**
309    * @covers ::applyContextMapping
310    */
311   public function testApplyContextMappingMissingRequired() {
312     $context = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
313     $context->expects($this->never())
314       ->method('getContextValue');
315
316     $contexts = [
317       'name' => $context,
318     ];
319
320     $context_definition = $this->getMock('Drupal\Core\Plugin\Context\ContextDefinitionInterface');
321     $context_definition->expects($this->atLeastOnce())
322       ->method('isRequired')
323       ->willReturn(TRUE);
324
325     $plugin = $this->getMock('Drupal\Tests\Core\Plugin\TestConfigurableContextAwarePluginInterface');
326     $plugin->expects($this->once())
327       ->method('getContextMapping')
328       ->willReturn([]);
329     $plugin->expects($this->once())
330       ->method('getContextDefinitions')
331       ->will($this->returnValue(['hit' => $context_definition]));
332     $plugin->expects($this->never())
333       ->method('setContextValue');
334
335     // No context, so no cacheability metadata can be passed along.
336     $plugin->expects($this->never())
337       ->method('getContext');
338
339     $this->setExpectedException(ContextException::class, 'Required contexts without a value: hit.');
340     $this->contextHandler->applyContextMapping($plugin, $contexts);
341   }
342
343   /**
344    * @covers ::applyContextMapping
345    */
346   public function testApplyContextMappingMissingNotRequired() {
347     $context = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
348     $context->expects($this->never())
349       ->method('getContextValue');
350
351     $contexts = [
352       'name' => $context,
353     ];
354
355     $context_definition = $this->getMock('Drupal\Core\Plugin\Context\ContextDefinitionInterface');
356     $context_definition->expects($this->atLeastOnce())
357       ->method('isRequired')
358       ->willReturn(FALSE);
359
360     $plugin = $this->getMock('Drupal\Tests\Core\Plugin\TestConfigurableContextAwarePluginInterface');
361     $plugin->expects($this->once())
362       ->method('getContextMapping')
363       ->willReturn(['optional' => 'missing']);
364     $plugin->expects($this->once())
365       ->method('getContextDefinitions')
366       ->will($this->returnValue(['optional' => $context_definition]));
367     $plugin->expects($this->never())
368       ->method('setContextValue');
369
370     // No context, so no cacheability metadata can be passed along.
371     $plugin->expects($this->never())
372       ->method('getContext');
373
374     $this->contextHandler->applyContextMapping($plugin, $contexts);
375   }
376
377   /**
378    * @covers ::applyContextMapping
379    */
380   public function testApplyContextMappingNoValueRequired() {
381     $context = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
382     $context->expects($this->never())
383       ->method('getContextValue');
384     $context->expects($this->atLeastOnce())
385       ->method('hasContextValue')
386       ->willReturn(FALSE);
387
388     $contexts = [
389       'hit' => $context,
390     ];
391
392     $context_definition = $this->getMock('Drupal\Core\Plugin\Context\ContextDefinitionInterface');
393     $context_definition->expects($this->atLeastOnce())
394       ->method('isRequired')
395       ->willReturn(TRUE);
396
397     $plugin = $this->getMock('Drupal\Tests\Core\Plugin\TestConfigurableContextAwarePluginInterface');
398     $plugin->expects($this->once())
399       ->method('getContextMapping')
400       ->willReturn([]);
401     $plugin->expects($this->once())
402       ->method('getContextDefinitions')
403       ->will($this->returnValue(['hit' => $context_definition]));
404     $plugin->expects($this->never())
405       ->method('setContextValue');
406
407     $this->setExpectedException(ContextException::class, 'Required contexts without a value: hit.');
408     $this->contextHandler->applyContextMapping($plugin, $contexts);
409   }
410
411
412   /**
413    * @covers ::applyContextMapping
414    */
415   public function testApplyContextMappingNoValueNonRequired() {
416     $context = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
417     $context->expects($this->never())
418       ->method('getContextValue');
419     $context->expects($this->atLeastOnce())
420       ->method('hasContextValue')
421       ->willReturn(FALSE);
422
423     $contexts = [
424       'hit' => $context,
425     ];
426
427     $context_definition = $this->getMock('Drupal\Core\Plugin\Context\ContextDefinitionInterface');
428     $context_definition->expects($this->atLeastOnce())
429       ->method('isRequired')
430       ->willReturn(FALSE);
431
432     $plugin = $this->getMock('Drupal\Tests\Core\Plugin\TestConfigurableContextAwarePluginInterface');
433     $plugin->expects($this->once())
434       ->method('getContextMapping')
435       ->willReturn([]);
436     $plugin->expects($this->once())
437       ->method('getContextDefinitions')
438       ->will($this->returnValue(['hit' => $context_definition]));
439     $plugin->expects($this->never())
440       ->method('setContextValue');
441
442     $this->contextHandler->applyContextMapping($plugin, $contexts);
443   }
444
445   /**
446    * @covers ::applyContextMapping
447    */
448   public function testApplyContextMappingConfigurableAssigned() {
449     $context_data = StringData::createInstance(DataDefinition::create('string'));
450     $context_data->setValue('foo');
451     $context = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
452     $context->expects($this->atLeastOnce())
453       ->method('getContextData')
454       ->will($this->returnValue($context_data));
455     $context->expects($this->atLeastOnce())
456       ->method('hasContextValue')
457       ->willReturn(TRUE);
458
459     $contexts = [
460       'name' => $context,
461     ];
462
463     $context_definition = $this->getMock('Drupal\Core\Plugin\Context\ContextDefinitionInterface');
464
465     $plugin = $this->getMock('Drupal\Tests\Core\Plugin\TestConfigurableContextAwarePluginInterface');
466     $plugin->expects($this->once())
467       ->method('getContextMapping')
468       ->willReturn([]);
469     $plugin->expects($this->once())
470       ->method('getContextDefinitions')
471       ->will($this->returnValue(['hit' => $context_definition]));
472     $plugin->expects($this->once())
473       ->method('setContextValue')
474       ->with('hit', $context_data);
475
476     // Make sure that the cacheability metadata is passed to the plugin context.
477     $plugin_context = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
478     $plugin_context->expects($this->once())
479       ->method('addCacheableDependency')
480       ->with($context);
481     $plugin->expects($this->once())
482       ->method('getContext')
483       ->with('hit')
484       ->willReturn($plugin_context);
485
486     $this->contextHandler->applyContextMapping($plugin, $contexts, ['hit' => 'name']);
487   }
488
489   /**
490    * @covers ::applyContextMapping
491    */
492   public function testApplyContextMappingConfigurableAssignedMiss() {
493     $context = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
494     $context->expects($this->never())
495       ->method('getContextValue');
496
497     $contexts = [
498       'name' => $context,
499     ];
500
501     $context_definition = $this->getMock('Drupal\Core\Plugin\Context\ContextDefinitionInterface');
502
503     $plugin = $this->getMock('Drupal\Tests\Core\Plugin\TestConfigurableContextAwarePluginInterface');
504     $plugin->expects($this->once())
505       ->method('getContextMapping')
506       ->willReturn([]);
507     $plugin->expects($this->once())
508       ->method('getContextDefinitions')
509       ->will($this->returnValue(['hit' => $context_definition]));
510     $plugin->expects($this->never())
511       ->method('setContextValue');
512
513     $this->setExpectedException(ContextException::class, 'Assigned contexts were not satisfied: miss');
514     $this->contextHandler->applyContextMapping($plugin, $contexts, ['miss' => 'name']);
515   }
516
517 }
518
519 interface TestConfigurableContextAwarePluginInterface extends ContextAwarePluginInterface, ConfigurablePluginInterface {
520 }