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