From d90d0ebbda976d7bd18b49bddef989de51d20ed5 Mon Sep 17 00:00:00 2001
From: Alex Pott <alex.a.pott@googlemail.com>
Date: Sun, 23 Jun 2019 12:49:55 +0100
Subject: [PATCH] Issue #2979044 by BR0kEN, alexpott: Unable to change menu
 items weight when the "system.site.weight_select_max" exceeded and
 "#pre_render" for "number" element is modified

---
 .../lib/Drupal/Core/Render/Element/Weight.php | 16 ++--
 .../element_info_test.info.yml                |  5 ++
 .../element_info_test.module                  | 24 ++++++
 .../Core/Render/Element/WeightTest.php        | 76 ++++++++++++++++++-
 4 files changed, 110 insertions(+), 11 deletions(-)
 create mode 100644 core/modules/system/tests/modules/element_info_test/element_info_test.info.yml
 create mode 100644 core/modules/system/tests/modules/element_info_test/element_info_test.module

diff --git a/core/lib/Drupal/Core/Render/Element/Weight.php b/core/lib/Drupal/Core/Render/Element/Weight.php
index 649de315fd72..029c9e4a4975 100644
--- a/core/lib/Drupal/Core/Render/Element/Weight.php
+++ b/core/lib/Drupal/Core/Render/Element/Weight.php
@@ -45,16 +45,16 @@ public function getInfo() {
   }
 
   /**
-   * Expands a weight element into a select element.
+   * Expands a weight element into a select/number element.
    */
   public static function processWeight(&$element, FormStateInterface $form_state, &$complete_form) {
+    // If the number of options is small enough, use a select field. Otherwise,
+    // use a number field.
+    $type = $element['#delta'] <= \Drupal::config('system.site')->get('weight_select_max') ? 'select' : 'number';
+    $element = array_merge($element, \Drupal::service('element_info')->getInfo($type));
     $element['#is_weight'] = TRUE;
 
-    $element_info_manager = \Drupal::service('element_info');
-    // If the number of options is small enough, use a select field.
-    $max_elements = \Drupal::config('system.site')->get('weight_select_max');
-    if ($element['#delta'] <= $max_elements) {
-      $element['#type'] = 'select';
+    if ($type === 'select') {
       $weights = [];
       for ($n = (-1 * $element['#delta']); $n <= $element['#delta']; $n++) {
         $weights[$n] = $n;
@@ -65,14 +65,10 @@ public static function processWeight(&$element, FormStateInterface $form_state,
         ksort($weights);
       }
       $element['#options'] = $weights;
-      $element += $element_info_manager->getInfo('select');
     }
-    // Otherwise, use a text field.
     else {
-      $element['#type'] = 'number';
       // Use a field big enough to fit most weights.
       $element['#size'] = 10;
-      $element += $element_info_manager->getInfo('number');
     }
 
     return $element;
diff --git a/core/modules/system/tests/modules/element_info_test/element_info_test.info.yml b/core/modules/system/tests/modules/element_info_test/element_info_test.info.yml
new file mode 100644
index 000000000000..9e43148ee610
--- /dev/null
+++ b/core/modules/system/tests/modules/element_info_test/element_info_test.info.yml
@@ -0,0 +1,5 @@
+name: 'Element info test'
+type: module
+package: Testing
+version: VERSION
+core: 8.x
diff --git a/core/modules/system/tests/modules/element_info_test/element_info_test.module b/core/modules/system/tests/modules/element_info_test/element_info_test.module
new file mode 100644
index 000000000000..58ebc889e2a0
--- /dev/null
+++ b/core/modules/system/tests/modules/element_info_test/element_info_test.module
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * @file
+ * Element info test.
+ */
+
+/**
+ * Implements hook_element_info_alter().
+ */
+function element_info_test_element_info_alter(array &$info) {
+  $info['number'] += ['#pre_render' => []];
+  /* @see \Drupal\KernelTests\Core\Render\Element\WeightTest::testProcessWeightSelectMax() */
+  $info['number']['#pre_render'][] = 'element_info_test_element_pre_render';
+}
+
+/**
+ * {@inheritdoc}
+ *
+ * @see \Drupal\KernelTests\Core\Render\Element\WeightTest::testProcessWeightSelectMax()
+ */
+function element_info_test_element_pre_render(array $element) {
+  return $element;
+}
diff --git a/core/tests/Drupal/KernelTests/Core/Render/Element/WeightTest.php b/core/tests/Drupal/KernelTests/Core/Render/Element/WeightTest.php
index 123c5e652145..b44b98cf1283 100644
--- a/core/tests/Drupal/KernelTests/Core/Render/Element/WeightTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Render/Element/WeightTest.php
@@ -3,6 +3,8 @@
 namespace Drupal\KernelTests\Core\Render\Element;
 
 use Drupal\Core\Form\FormState;
+use Drupal\Core\Render\Element\Number;
+use Drupal\Core\Render\Element\Select;
 use Drupal\Core\Render\Element\Weight;
 use Drupal\KernelTests\KernelTestBase;
 
@@ -15,7 +17,7 @@ class WeightTest extends KernelTestBase {
   /**
    * {@inheritdoc}
    */
-  protected static $modules = ['system'];
+  protected static $modules = ['system', 'element_info_test'];
 
   /**
    * {@inheritdoc}
@@ -49,4 +51,76 @@ public function testProcessWeight() {
     );
   }
 
+  /**
+   * Test transformation from "select" to "number" for MAX_DELTA + 1.
+   *
+   * @throws \Exception
+   *
+   * @covers ::processWeight
+   */
+  public function testProcessWeightSelectMax() {
+    $form_state = new FormState();
+    $definition = [
+      '#type' => 'weight',
+      '#delta' => $this->container
+        ->get('config.factory')
+        ->get('system.site')
+        ->get('weight_select_max'),
+      // Expected by the "doBuildForm()" method of "form_builder" service.
+      '#parents' => [],
+    ];
+
+    $assert = function ($type, array $element, array $expected) use ($form_state) {
+      // Pretend we have a form to trigger the "#process" callbacks.
+      $element = $this->container
+        ->get('form_builder')
+        ->doBuildForm(__FUNCTION__, $element, $form_state);
+
+      $expected['#type'] = $type;
+
+      foreach ($expected as $property => $value) {
+        static::assertSame($value, $element[$property]);
+      }
+
+      return $element;
+    };
+
+    // When the "#delta" is less or equal to maximum the "weight" must be
+    // rendered as a "select".
+    $select = $definition;
+    $assert('select', $select, [
+      '#process' => [
+        [Select::class, 'processSelect'],
+        [Select::class, 'processAjaxForm'],
+      ],
+      '#pre_render' => [
+        [Select::class, 'preRenderSelect'],
+      ],
+    ]);
+
+    $number = $definition;
+    // Increase "#delta" in order to start rendering "number" elements
+    // instead of "select".
+    $number['#delta']++;
+    // The "number" element definition has the "#pre_render" declaration by
+    // default. The "hook_element_info_alter()" allows to modify the definition
+    // of an element. We must be sure the standard "#pre_render" callbacks
+    // are presented (unless explicitly removed) even in a case when the array
+    // is modified by the alter hook.
+    $assert('number', $number, [
+      '#process' => [
+        [Number::class, 'processAjaxForm'],
+      ],
+      '#element_validate' => [
+        [Number::class, 'validateNumber'],
+      ],
+      '#pre_render' => [
+        [Number::class, 'preRenderNumber'],
+        // The custom callback is appended.
+        /* @see element_info_test_element_info_alter() */
+        'element_info_test_element_pre_render',
+      ],
+    ]);
+  }
+
 }
-- 
GitLab