Skip to content

fix: copy labels to runtime contexts#32

Merged
e0ipso merged 1 commit into
mainfrom
fix/context-mapping-null-labels
May 25, 2026
Merged

fix: copy labels to runtime contexts#32
e0ipso merged 1 commit into
mainfrom
fix/context-mapping-null-labels

Conversation

@e0ipso
Copy link
Copy Markdown
Member

@e0ipso e0ipso commented May 25, 2026

Summary

  • Runtime context definitions (e.g. NodeRouteContext) may lack labels, leaving select option values as NULL
  • Drupal's FormValidator::performRequiredValidation() uses isset() to validate select submissions, which returns FALSE for NULL values — rejecting the context mapping choice even though the array key exists
  • This caused two visible issues: a blank (unlabeled) option in the context mapping dropdown, and the error "The submitted value @node.node_route_context:node in the Node element is not allowed" when saving
  • Fix: in getGatheredContexts(), copy labels from available context definitions (which always carry human-readable labels) onto runtime context definitions that lack them

Test plan

  • Go to a Layout Builder page and add a Proxy Block
  • Select a target block that requires a node context (e.g. "Insider Exclusive Stories")
  • Verify the context mapping dropdown shows a labeled option ("Node from URL"), not a blank one
  • Select "Node from URL" and click "Add block"
  • Verify the block saves without a validation error
  • Run 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

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.
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +49 to +56
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);
}
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The runtime context definition mock should use ContextDefinitionInterface because the code under test calls setLabel(), which is defined in the Core interface but not in the Component interface.

    $definition = $this->createMock(ContextDefinitionInterface::class);

]);

// Runtime context has a definition that starts with a NULL label.
$runtime_definition = $this->createMock(ComponentContextDefinitionInterface::class);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This mock specifically expects a call to setLabel(). You should use ContextDefinitionInterface::class here as ComponentContextDefinitionInterface does not define this method.

    $runtime_definition = $this->createMock(ContextDefinitionInterface::class);

]);

// Runtime context already has a label.
$runtime_definition = $this->createMock(ComponentContextDefinitionInterface::class);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For consistency with other context definition mocks in this test class, use ContextDefinitionInterface::class instead of the component-level interface.

    $runtime_definition = $this->createMock(ContextDefinitionInterface::class);

@e0ipso e0ipso merged commit 6f3131c into main May 25, 2026
7 checks passed
@github-actions
Copy link
Copy Markdown

🎉 This PR is included in version 1.6.3 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant