From 7a695724a3a2a53d4d7ac88abbf3b32b017ee8af Mon Sep 17 00:00:00 2001
From: webchick <webchick@24967.no-reply.drupal.org>
Date: Thu, 6 Feb 2014 20:26:52 -0800
Subject: [PATCH] Issue #2152207 by steveoliver, joelpittet, gnuget, idflood,
 hussainweb, shanethehat, jenlampton, kpa, AnythonyR, EVIIILJ, kgoel, Cottser,
 dsdeiz, hanpersand: Convert theme_details() to Twig

---
 core/includes/form.inc                        | 50 ++++++++-----------
 core/includes/theme.inc                       |  1 +
 .../Drupal/system/Tests/Common/RenderTest.php | 44 ++++++++++++----
 .../system/templates/details.html.twig        | 33 ++++++++++++
 4 files changed, 87 insertions(+), 41 deletions(-)
 create mode 100644 core/modules/system/templates/details.html.twig

diff --git a/core/includes/form.inc b/core/includes/form.inc
index 35879a3a2fdc..d0231a2dcc59 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -979,44 +979,34 @@ function theme_fieldset($variables) {
 }
 
 /**
- * Returns HTML for a details form element and its children.
+ * Prepares variables for details element templates.
  *
- * @param $variables
+ * Default template: details.html.twig.
+ *
+ * @param array $variables
  *   An associative array containing:
  *   - element: An associative array containing the properties of the element.
- *     Properties used: #attributes, #children, #collapsed, #description, #id,
- *     #title, #value.
+ *     Properties used: #attributes, #children, #collapsed, #collapsible,
+ *     #description, #id, #title, #value.
  *
  * @ingroup themeable
  */
-function theme_details($variables) {
+function template_preprocess_details(&$variables) {
   $element = $variables['element'];
-  element_set_attributes($element, array('id'));
-  _form_set_attributes($element, array('form-wrapper'));
-
-  $output = '<details' . new Attribute($element['#attributes']) . '>';
+  $variables['attributes'] = $element['#attributes'];
+  $variables['summary_attributes'] = new Attribute();
   if (!empty($element['#title'])) {
-    $summary_attributes = new Attribute(array(
-      'role' => 'button',
-    ));
+    $variables['summary_attributes']['role'] = 'button';
     if (!empty($element['#attributes']['id'])) {
-      $summary_attributes['aria-controls'] = $element['#attributes']['id'];
+      $variables['summary_attributes']['aria-controls'] = $element['#attributes']['id'];
     }
-    $summary_attributes['aria-expanded'] = empty($element['#attributes']['open']) ? FALSE : TRUE;
-    $summary_attributes['aria-pressed'] = $summary_attributes['aria-expanded'];
-    $output .= '<summary' . $summary_attributes . '>' . $element['#title'] . '</summary>';
-  }
-  $output .= '<div class="details-wrapper">';
-  if (!empty($element['#description'])) {
-    $output .= '<div class="details-description">' . $element['#description'] . '</div>';
+    $variables['summary_attributes']['aria-expanded'] = empty($element['#attributes']['open']) ? FALSE : TRUE;
+    $variables['summary_attributes']['aria-pressed'] = $variables['summary_attributes']['aria-expanded'];
   }
-  $output .= $element['#children'];
-  if (isset($element['#value'])) {
-    $output .= $element['#value'];
-  }
-  $output .= '</div>';
-  $output .= "</details>\n";
-  return $output;
+  $variables['title'] = (!empty($element['#title'])) ? $element['#title'] : '';
+  $variables['description'] = (!empty($element['#description'])) ? $element['#description'] : '';
+  $variables['children'] = (isset($element['#children'])) ? $element['#children'] : '';
+  $variables['value'] = (isset($element['#value'])) ? $element['#value'] : '';
 }
 
 /**
@@ -1980,11 +1970,11 @@ function form_process_group(&$element, &$form_state) {
  *   The modified element.
  */
 function form_pre_render_details($element) {
+  element_set_attributes($element, array('id'));
+
   // The .form-wrapper class is required for #states to treat details like
   // containers.
-  if (!isset($element['#attributes']['class'])) {
-    $element['#attributes']['class'] = array();
-  }
+  _form_set_attributes($element, array('form-wrapper'));
 
   // Collapsible details.
   $element['#attached']['library'][] = array('system', 'drupal.collapse');
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index 7065a805ae2a..bc1fbbfe0865 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -2682,6 +2682,7 @@ function drupal_common_theme() {
     ),
     'details' => array(
       'render element' => 'element',
+      'template' => 'details',
     ),
     'radios' => array(
       'render element' => 'element',
diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/RenderTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/RenderTest.php
index 750250d3f4a9..a42f111bcfee 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Common/RenderTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Common/RenderTest.php
@@ -598,9 +598,6 @@ function testDrupalRenderChildrenPostRenderCache() {
     $element = array('#cache' => $element['#cache']);
     $cached_element = cache()->get(drupal_render_cid_create($element))->data;
     $expected_element = array(
-      '#markup' => '<details class="form-wrapper" open="open"><summary role="button" aria-expanded>Parent</summary><div class="details-wrapper"><details class="form-wrapper" open="open"><summary role="button" aria-expanded>Child</summary><div class="details-wrapper">Subchild</div></details>
-</div></details>
-',
       '#attached' => array(
         'js' => array(
           array('type' => 'setting', 'data' => array('foo' => 'bar'))
@@ -618,7 +615,17 @@ function testDrupalRenderChildrenPostRenderCache() {
         )
       ),
     );
-    $this->assertIdentical($cached_element, $expected_element, 'The correct data is cached: the stored #markup and #attached properties are not affected by #post_render_cache callbacks.');
+
+    $dom = filter_dom_load($cached_element['#markup']);
+    $xpath = new \DOMXPath($dom);
+    $parent = $xpath->query('//details[@class="form-wrapper" and @open="open"]/summary[@role="button" and @aria-expanded and text()="Parent"]')->length;
+    $child =  $xpath->query('//details[@class="form-wrapper" and @open="open"]/div[@class="details-wrapper"]/details[@class="form-wrapper" and @open="open"]/summary[@role="button" and @aria-expanded and text()="Child"]')->length;
+    $subchild = $xpath->query('//details[@class="form-wrapper" and @open="open"]/div[@class="details-wrapper"]/details[@class="form-wrapper" and @open="open"]/div [@class="details-wrapper" and text()="Subchild"]')->length;
+    $this->assertTrue($parent && $child && $subchild, 'The correct data is cached: the stored #markup is not affected by #post_render_cache callbacks.');
+
+    // Remove markup because it's compared above in the xpath.
+    unset($cached_element['#markup']);
+    $this->assertIdentical($cached_element, $expected_element, 'The correct data is cached: the stored #attached properties are not affected by #post_render_cache callbacks.');
 
     // GET request: #cache enabled, cache hit.
     drupal_static_reset('_drupal_add_js');
@@ -674,9 +681,6 @@ function testDrupalRenderChildrenPostRenderCache() {
     $cached_parent_element = cache()->get(drupal_render_cid_create($element))->data;
     $cached_child_element = cache()->get(drupal_render_cid_create($element['child']))->data;
     $expected_parent_element = array(
-      '#markup' => '<details class="form-wrapper" open="open"><summary role="button" aria-expanded>Parent</summary><div class="details-wrapper"><details class="form-wrapper" open="open"><summary role="button" aria-expanded>Child</summary><div class="details-wrapper">Subchild</div></details>
-</div></details>
-',
       '#attached' => array(
         'js' => array(
           array('type' => 'setting', 'data' => array('foo' => 'bar'))
@@ -694,10 +698,19 @@ function testDrupalRenderChildrenPostRenderCache() {
         )
       ),
     );
-    $this->assertIdentical($cached_parent_element, $expected_parent_element, 'The correct data is cached for the parent: the stored #markup and #attached properties are not affected by #post_render_cache callbacks.');
+
+    $dom = filter_dom_load($cached_parent_element['#markup']);
+    $xpath = new \DOMXPath($dom);
+    $parent = $xpath->query('//details[@class="form-wrapper" and @open="open"]/summary[@role="button" and @aria-expanded and text()="Parent"]')->length;
+    $child =  $xpath->query('//details[@class="form-wrapper" and @open="open"]/div[@class="details-wrapper"]/details[@class="form-wrapper" and @open="open"]/summary[@role="button" and @aria-expanded and text()="Child"]')->length;
+    $subchild = $xpath->query('//details[@class="form-wrapper" and @open="open"]/div[@class="details-wrapper"]/details[@class="form-wrapper" and @open="open"]/div [@class="details-wrapper" and text()="Subchild"]')->length;
+    $this->assertTrue($parent && $child && $subchild, 'The correct data is cached for the parent: the stored #markup is not affected by #post_render_cache callbacks.');
+
+    // Remove markup because it's compared above in the xpath.
+    unset($cached_parent_element['#markup']);
+    $this->assertIdentical($cached_parent_element, $expected_parent_element, 'The correct data is cached for the parent: the stored #attached properties are not affected by #post_render_cache callbacks.');
+
     $expected_child_element = array(
-      '#markup' => '<details class="form-wrapper" open="open"><summary role="button" aria-expanded>Child</summary><div class="details-wrapper">Subchild</div></details>
-',
       '#attached' => array(
         'library' => array(
           array('system', 'drupal.collapse'),
@@ -710,7 +723,16 @@ function testDrupalRenderChildrenPostRenderCache() {
         )
       ),
     );
-    $this->assertIdentical($cached_child_element, $expected_child_element, 'The correct data is cached for the child: the stored #markup and #attached properties are not affected by #post_render_cache callbacks.');
+
+    $dom = filter_dom_load($cached_child_element['#markup']);
+    $xpath = new \DOMXPath($dom);
+    $child =  $xpath->query('//details[@class="form-wrapper" and @open="open"]/summary[@role="button" and @aria-expanded and text()="Child"]')->length;
+    $subchild = $xpath->query('//details[@class="form-wrapper" and @open="open"]/div [@class="details-wrapper" and text()="Subchild"]')->length;
+    $this->assertTrue($child && $subchild, 'The correct data is cached for the child: the stored #markup is not affected by #post_render_cache callbacks.');
+
+    // Remove markup because it's compared above in the xpath.
+    unset($cached_child_element['#markup']);
+    $this->assertIdentical($cached_child_element, $expected_child_element, 'The correct data is cached for the child: the stored #attached properties are not affected by #post_render_cache callbacks.');
 
     // GET request: #cache enabled, cache hit, parent element.
     drupal_static_reset('_drupal_add_js');
diff --git a/core/modules/system/templates/details.html.twig b/core/modules/system/templates/details.html.twig
new file mode 100644
index 000000000000..17ea820dd7ab
--- /dev/null
+++ b/core/modules/system/templates/details.html.twig
@@ -0,0 +1,33 @@
+{#
+/**
+ * @file
+ * Default theme implementation for a details element.
+ *
+ * Available variables
+ * - attributes: A list of HTML attributes for the details element.
+ * - title: (optional) The title of the element, may not be set.
+ * - description: (optional) The description of the element, may not be set.
+ * - children: (optional) The children of the element, may not be set.
+ * - value: (optional) The value of the element, may not be set.
+ *
+ * @see template_preprocess_details()
+ *
+ * @ingroup themeable
+ */
+#}
+<details{{ attributes }}>
+  {%- if title -%}
+    <summary{{ summary_attributes }}>{{ title }}</summary>
+  {%- endif -%}
+  <div class="details-wrapper">
+    {%- if description -%}
+      <div class="details-description">{{ description }}</div>
+    {%- endif -%}
+    {%- if children -%}
+      {{ children }}
+    {%- endif -%}
+    {%- if value -%}
+      {{ value }}
+    {%- endif -%}
+  </div>
+</details>
-- 
GitLab