fix: copy labels to runtime contexts#32
Conversation
Runtime context definitions (e.g. NodeRouteContext) may lack labels, leaving select option values as NULL. Drupal's FormValidator uses isset() to validate select submissions, which returns FALSE for NULL — rejecting the choice even though the key exists. Copy labels from available context definitions (which always carry human-readable labels) onto any runtime definitions that lack them.
There was a problem hiding this comment.
Code Review
This pull request ensures that runtime context definitions have labels by copying them from available contexts when missing, which prevents Drupal form validation errors. The unit tests were refactored to include helper methods for mock creation and new test cases for label preservation. Feedback suggests optimizing the context gathering loop by caching definitions in a variable and updating unit test mocks to use the Core ContextDefinitionInterface instead of the Component-level interface to properly support the setLabel() method.
| foreach ($contexts as $context_id => $context) { | ||
| if (empty($context->getContextDefinition()->getLabel()) && isset($available_contexts[$context_id])) { | ||
| $available_label = $available_contexts[$context_id]->getContextDefinition()->getLabel(); | ||
| if (!empty($available_label)) { | ||
| $context->getContextDefinition()->setLabel($available_label); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
The loop performs multiple calls to getContextDefinition() on the same object. Assigning the definition to a variable improves readability and slightly improves performance by avoiding redundant method calls.
foreach ($contexts as $context_id => $context) {
$definition = $context->getContextDefinition();
if (empty($definition->getLabel()) && isset($available_contexts[$context_id])) {
$available_label = $available_contexts[$context_id]->getContextDefinition()->getLabel();
if (!empty($available_label)) {
$definition->setLabel($available_label);
}
}
}| private function createAvailableContextMocks(array $labels): array { | ||
| $contexts = []; | ||
| foreach ($labels as $id => $label) { | ||
| $definition = $this->createMock(ComponentContextDefinitionInterface::class); |
There was a problem hiding this comment.
Since this service interacts with Drupal Core's context system, it is better to use ContextDefinitionInterface for mocking. This ensures that the mock objects are consistent with the types expected by the Core services and provides access to methods like setLabel() if needed.
$definition = $this->createMock(ContextDefinitionInterface::class);| * A mock context. | ||
| */ | ||
| private function createRuntimeContextMock(?string $label = 'Test'): ContextInterface|MockObject { | ||
| $definition = $this->createMock(ComponentContextDefinitionInterface::class); |
There was a problem hiding this comment.
| ]); | ||
|
|
||
| // Runtime context has a definition that starts with a NULL label. | ||
| $runtime_definition = $this->createMock(ComponentContextDefinitionInterface::class); |
| ]); | ||
|
|
||
| // Runtime context already has a label. | ||
| $runtime_definition = $this->createMock(ComponentContextDefinitionInterface::class); |
|
🎉 This PR is included in version 1.6.3 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
Summary
NodeRouteContext) may lack labels, leaving select option values asNULLFormValidator::performRequiredValidation()usesisset()to validate select submissions, which returnsFALSEforNULLvalues — rejecting the context mapping choice even though the array key existsgetGatheredContexts(), copy labels from available context definitions (which always carry human-readable labels) onto runtime context definitions that lack themTest plan
vendor/bin/phpunit --debug -c web/core/phpunit.xml.dist web/modules/contrib/proxy_block/tests/src/Unit/TargetBlockContextManagerTest.php— all 17 tests should pass