5 * Contains \Drupal\Tests\Core\Plugin\ContextHandlerTest.
8 namespace Drupal\Tests\Core\Plugin;
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;
20 * @coversDefaultClass \Drupal\Core\Plugin\Context\ContextHandler
23 class ContextHandlerTest extends UnitTestCase {
26 * The context handler.
28 * @var \Drupal\Core\Plugin\Context\ContextHandler
30 protected $contextHandler;
35 protected function setUp() {
38 $this->contextHandler = new ContextHandler();
42 * @covers ::checkRequirements
44 * @dataProvider providerTestCheckRequirements
46 public function testCheckRequirements($contexts, $requirements, $expected) {
47 $this->assertSame($expected, $this->contextHandler->checkRequirements($contexts, $requirements));
51 * Provides data for testCheckRequirements().
53 public function providerTestCheckRequirements() {
54 $requirement_optional = new ContextDefinition();
55 $requirement_optional->setRequired(FALSE);
57 $requirement_any = new ContextDefinition();
58 $requirement_any->setRequired(TRUE);
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')));
65 $requirement_specific = new ContextDefinition('specific');
66 $requirement_specific->setConstraints(['bar' => 'baz']);
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')));
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));
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];
98 * @covers ::getMatchingContexts
100 * @dataProvider providerTestGetMatchingContexts
102 public function testGetMatchingContexts($contexts, $requirement, $expected = NULL) {
103 if (is_null($expected)) {
104 $expected = $contexts;
106 $this->assertSame($expected, $this->contextHandler->getMatchingContexts($contexts, $requirement));
110 * Provides data for testGetMatchingContexts().
112 public function providerTestGetMatchingContexts() {
113 $requirement_any = new ContextDefinition();
115 $requirement_specific = new ContextDefinition('specific');
116 $requirement_specific->setConstraints(['bar' => 'baz']);
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));
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];
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, []];
154 * @covers ::filterPluginDefinitionsByContexts
156 * @dataProvider providerTestFilterPluginDefinitionsByContexts
158 public function testFilterPluginDefinitionsByContexts($has_context, $definitions, $expected) {
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];
171 $this->assertSame($expected, $this->contextHandler->filterPluginDefinitionsByContexts($contexts, $definitions));
175 * Provides data for testFilterPluginDefinitionsByContexts().
177 public function providerTestFilterPluginDefinitionsByContexts() {
181 // No context and no plugins, no plugins available.
182 $data[] = [FALSE, $plugins, []];
184 $plugins = ['expected_plugin' => []];
185 // No context, all plugins available.
186 $data[] = [FALSE, $plugins, $plugins];
188 $plugins = ['expected_plugin' => ['context' => []]];
189 // No context, all plugins available.
190 $data[] = [FALSE, $plugins, $plugins];
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];
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, []];
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];
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];
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];
220 $unexpected_context_definition = (new ContextDefinition('unexpected_data_type'))->setConstraints(['mismatched_constraint_name' => 'mismatched_constraint_value']);
222 'unexpected_plugin' => ['context' => ['context1' => $unexpected_context_definition]],
223 'expected_plugin' => ['context' => ['context2' => new ContextDefinition('expected_data_type')]],
225 // Context only satisfies one plugin.
226 $data[] = [TRUE, $plugins, ['expected_plugin' => $plugins['expected_plugin']]];
232 * @covers ::applyContextMapping
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')
246 $context_miss = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
247 $context_miss->expects($this->never())
248 ->method('getContextData');
251 'hit' => $context_hit,
252 'miss' => $context_miss,
255 $context_definition = $this->getMock('Drupal\Core\Plugin\Context\ContextDefinitionInterface');
257 $plugin = $this->getMock('Drupal\Core\Plugin\ContextAwarePluginInterface');
258 $plugin->expects($this->once())
259 ->method('getContextMapping')
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);
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')
276 ->willReturn($plugin_context);
278 $this->contextHandler->applyContextMapping($plugin, $contexts);
282 * @covers ::applyContextMapping
284 public function testApplyContextMappingMissingRequired() {
285 $context = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
286 $context->expects($this->never())
287 ->method('getContextValue');
293 $context_definition = $this->getMock('Drupal\Core\Plugin\Context\ContextDefinitionInterface');
294 $context_definition->expects($this->atLeastOnce())
295 ->method('isRequired')
298 $plugin = $this->getMock('Drupal\Tests\Core\Plugin\TestConfigurableContextAwarePluginInterface');
299 $plugin->expects($this->once())
300 ->method('getContextMapping')
302 $plugin->expects($this->once())
303 ->method('getContextDefinitions')
304 ->will($this->returnValue(['hit' => $context_definition]));
305 $plugin->expects($this->never())
306 ->method('setContextValue');
308 // No context, so no cacheability metadata can be passed along.
309 $plugin->expects($this->never())
310 ->method('getContext');
312 $this->setExpectedException(ContextException::class, 'Required contexts without a value: hit.');
313 $this->contextHandler->applyContextMapping($plugin, $contexts);
317 * @covers ::applyContextMapping
319 public function testApplyContextMappingMissingNotRequired() {
320 $context = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
321 $context->expects($this->never())
322 ->method('getContextValue');
328 $context_definition = $this->getMock('Drupal\Core\Plugin\Context\ContextDefinitionInterface');
329 $context_definition->expects($this->atLeastOnce())
330 ->method('isRequired')
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');
343 // No context, so no cacheability metadata can be passed along.
344 $plugin->expects($this->never())
345 ->method('getContext');
347 $this->contextHandler->applyContextMapping($plugin, $contexts);
351 * @covers ::applyContextMapping
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')
365 $context_definition = $this->getMock('Drupal\Core\Plugin\Context\ContextDefinitionInterface');
366 $context_definition->expects($this->atLeastOnce())
367 ->method('isRequired')
370 $plugin = $this->getMock('Drupal\Tests\Core\Plugin\TestConfigurableContextAwarePluginInterface');
371 $plugin->expects($this->once())
372 ->method('getContextMapping')
374 $plugin->expects($this->once())
375 ->method('getContextDefinitions')
376 ->will($this->returnValue(['hit' => $context_definition]));
377 $plugin->expects($this->never())
378 ->method('setContextValue');
380 $this->setExpectedException(ContextException::class, 'Required contexts without a value: hit.');
381 $this->contextHandler->applyContextMapping($plugin, $contexts);
386 * @covers ::applyContextMapping
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')
400 $context_definition = $this->getMock('Drupal\Core\Plugin\Context\ContextDefinitionInterface');
401 $context_definition->expects($this->atLeastOnce())
402 ->method('isRequired')
405 $plugin = $this->getMock('Drupal\Tests\Core\Plugin\TestConfigurableContextAwarePluginInterface');
406 $plugin->expects($this->once())
407 ->method('getContextMapping')
409 $plugin->expects($this->once())
410 ->method('getContextDefinitions')
411 ->will($this->returnValue(['hit' => $context_definition]));
412 $plugin->expects($this->never())
413 ->method('setContextValue');
415 $this->contextHandler->applyContextMapping($plugin, $contexts);
419 * @covers ::applyContextMapping
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')
436 $context_definition = $this->getMock('Drupal\Core\Plugin\Context\ContextDefinitionInterface');
438 $plugin = $this->getMock('Drupal\Tests\Core\Plugin\TestConfigurableContextAwarePluginInterface');
439 $plugin->expects($this->once())
440 ->method('getContextMapping')
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);
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')
454 $plugin->expects($this->once())
455 ->method('getContext')
457 ->willReturn($plugin_context);
459 $this->contextHandler->applyContextMapping($plugin, $contexts, ['hit' => 'name']);
463 * @covers ::applyContextMapping
465 public function testApplyContextMappingConfigurableAssignedMiss() {
466 $context = $this->getMock('Drupal\Core\Plugin\Context\ContextInterface');
467 $context->expects($this->never())
468 ->method('getContextValue');
474 $context_definition = $this->getMock('Drupal\Core\Plugin\Context\ContextDefinitionInterface');
476 $plugin = $this->getMock('Drupal\Tests\Core\Plugin\TestConfigurableContextAwarePluginInterface');
477 $plugin->expects($this->once())
478 ->method('getContextMapping')
480 $plugin->expects($this->once())
481 ->method('getContextDefinitions')
482 ->will($this->returnValue(['hit' => $context_definition]));
483 $plugin->expects($this->never())
484 ->method('setContextValue');
486 $this->setExpectedException(ContextException::class, 'Assigned contexts were not satisfied: miss');
487 $this->contextHandler->applyContextMapping($plugin, $contexts, ['miss' => 'name']);
492 interface TestConfigurableContextAwarePluginInterface extends ContextAwarePluginInterface, ConfigurablePluginInterface {