From d0db869bf11f73448d17cb8e3b3c9dd68ccfa497 Mon Sep 17 00:00:00 2001
From: catch <catch@35733.no-reply.drupal.org>
Date: Wed, 28 Sep 2011 03:15:03 +0900
Subject: [PATCH] Issue #561858 by effulgentsia, sun, rfay, Nick_vh,
 merlinofchaos, katbailey, dereine et al: documentation and tests for
 drupal_add_js() and drupal_add_css() with the AJAX framework.

---
 includes/ajax.inc                             | 17 ++----
 modules/simpletest/drupal_web_test_case.php   | 12 +++-
 modules/simpletest/tests/ajax.test            | 58 +++++++++++++++++++
 .../simpletest/tests/ajax_forms_test.module   | 41 +++++++++++++
 4 files changed, 115 insertions(+), 13 deletions(-)

diff --git a/includes/ajax.inc b/includes/ajax.inc
index d70808efe09f..cda55b424663 100644
--- a/includes/ajax.inc
+++ b/includes/ajax.inc
@@ -262,19 +262,13 @@ function ajax_render($commands = array()) {
     }
   }
 
-  // Settings are handled separately, later in this function, so that changes to
-  // the ajaxPageState setting that occur during drupal_get_css() and
-  // drupal_get_js() get included, and because the jQuery.extend() code produced
-  // by drupal_get_js() for adding settings isn't appropriate during an Ajax
-  // response, because it does not pass TRUE for the "deep" parameter, and
-  // therefore, can clobber existing settings on the page.
+  // Render the HTML to load these files, and add AJAX commands to insert this
+  // HTML in the page. We pass TRUE as the $skip_alter argument to prevent the
+  // data from being altered again, as we already altered it above. Settings are
+  // handled separately, afterwards.
   if (isset($items['js']['settings'])) {
     unset($items['js']['settings']);
   }
-
-  // Render the HTML to load these files, and add Ajax commands to insert this
-  // HTML in the page. We pass TRUE as the $skip_alter argument to prevent the
-  // data from being altered again, as we already altered it above.
   $styles = drupal_get_css($items['css'], TRUE);
   $scripts_footer = drupal_get_js('footer', $items['js'], TRUE);
   $scripts_header = drupal_get_js('header', $items['js'], TRUE);
@@ -293,11 +287,10 @@ function ajax_render($commands = array()) {
     $commands = array_merge($extra_commands, $commands);
   }
 
+  // Now add a command to merge changes and additions to Drupal.settings.
   $scripts = drupal_add_js();
   if (!empty($scripts['settings'])) {
     $settings = $scripts['settings'];
-    // Automatically extract any settings added via drupal_add_js() and make
-    // them the first command.
     array_unshift($commands, ajax_command_settings(call_user_func_array('array_merge_recursive', $settings['data']), TRUE));
   }
 
diff --git a/modules/simpletest/drupal_web_test_case.php b/modules/simpletest/drupal_web_test_case.php
index 5c2468cd5a72..a596216d9989 100644
--- a/modules/simpletest/drupal_web_test_case.php
+++ b/modules/simpletest/drupal_web_test_case.php
@@ -1968,6 +1968,16 @@ protected function drupalPostAJAX($path, $edit, $triggering_element, $ajax_path
       $id = (string) $element['id'];
       $extra_post .= '&' . urlencode('ajax_html_ids[]') . '=' . urlencode($id);
     }
+    if (isset($drupal_settings['ajaxPageState'])) {
+      $extra_post .= '&' . urlencode('ajax_page_state[theme]') . '=' . urlencode($drupal_settings['ajaxPageState']['theme']);
+      $extra_post .= '&' . urlencode('ajax_page_state[theme_token]') . '=' . urlencode($drupal_settings['ajaxPageState']['theme_token']);
+      foreach ($drupal_settings['ajaxPageState']['css'] as $key => $value) {
+        $extra_post .= '&' . urlencode("ajax_page_state[css][$key]") . '=1';
+      }
+      foreach ($drupal_settings['ajaxPageState']['js'] as $key => $value) {
+        $extra_post .= '&' . urlencode("ajax_page_state[js][$key]") . '=1';
+      }
+    }
 
     // Unless a particular path is specified, use the one specified by the
     // Ajax settings, or else 'system/ajax'.
@@ -1992,7 +2002,7 @@ protected function drupalPostAJAX($path, $edit, $triggering_element, $ajax_path
       foreach ($return as $command) {
         switch ($command['command']) {
           case 'settings':
-            $drupal_settings = array_merge_recursive($drupal_settings, $command['settings']);
+            $drupal_settings = drupal_array_merge_deep($drupal_settings, $command['settings']);
             break;
 
           case 'insert':
diff --git a/modules/simpletest/tests/ajax.test b/modules/simpletest/tests/ajax.test
index 957979249ad6..9a76b9692a93 100644
--- a/modules/simpletest/tests/ajax.test
+++ b/modules/simpletest/tests/ajax.test
@@ -116,6 +116,64 @@ class AJAXFrameworkTestCase extends AJAXTestCase {
     );
      $this->assertCommand($commands, $expected, t('Custom error message is output.'));
   }
+
+  /**
+   * Test that new JavaScript and CSS files added during an AJAX request are returned.
+   */
+  function testLazyLoad() {
+    $expected = array(
+      'setting_name' => 'ajax_forms_test_lazy_load_form_submit',
+      'setting_value' => 'executed',
+      'css' => drupal_get_path('module', 'system') . '/system.admin.css',
+      'js' => drupal_get_path('module', 'system') . '/system.js',
+    );
+
+    // Get the base page.
+    $this->drupalGet('ajax_forms_test_lazy_load_form');
+    $original_settings = $this->drupalGetSettings();
+    $original_css = $original_settings['ajaxPageState']['css'];
+    $original_js = $original_settings['ajaxPageState']['js'];
+
+    // Verify that the base page doesn't have the settings and files that are to
+    // be lazy loaded as part of the next request.
+    $this->assertTrue(!isset($original_settings[$expected['setting_name']]), t('Page originally lacks the %setting, as expected.', array('%setting' => $expected['setting_name'])));
+    $this->assertTrue(!isset($original_settings[$expected['css']]), t('Page originally lacks the %css file, as expected.', array('%css' => $expected['css'])));
+    $this->assertTrue(!isset($original_settings[$expected['js']]), t('Page originally lacks the %js file, as expected.', array('%js' => $expected['js'])));
+
+    // Submit the AJAX request.
+    $commands = $this->drupalPostAJAX(NULL, array(), array('op' => t('Submit')));
+    $new_settings = $this->drupalGetSettings();
+    $new_css = $new_settings['ajaxPageState']['css'];
+    $new_js = $new_settings['ajaxPageState']['js'];
+
+    // Verify the expected setting was added.
+    $this->assertIdentical($new_settings[$expected['setting_name']], $expected['setting_value'], t('Page now has the %setting.', array('%setting' => $expected['setting_name'])));
+
+    // Verify the expected CSS file was added, both to Drupal.settings, and as
+    // an AJAX command for inclusion into the HTML.
+    // @todo A drupal_css_defaults() function in Drupal 8 would be nice.
+    $expected_css_html = drupal_get_css(array($expected['css'] => array(
+      'type' => 'file',
+      'group' => CSS_DEFAULT,
+      'weight' => 0,
+      'every_page' => FALSE,
+      'media' => 'all',
+      'preprocess' => TRUE,
+      'data' => $expected['css'],
+      'browsers' => array('IE' => TRUE, '!IE' => TRUE),
+    )), TRUE);
+    $this->assertEqual($new_css, $original_css + array($expected['css'] => 1), t('Page state now has the %css file.', array('%css' => $expected['css'])));
+    $this->assertCommand($commands, array('data' => $expected_css_html), t('Page now has the %css file.', array('%css' => $expected['css'])));
+
+    // Verify the expected JS file was added, both to Drupal.settings, and as
+    // an AJAX command for inclusion into the HTML. By testing for an exact HTML
+    // string containing the SCRIPT tag, we also ensure that unexpected
+    // JavaScript code, such as a jQuery.extend() that would potentially clobber
+    // rather than properly merge settings, didn't accidentally get added.
+    $expected_js_html = drupal_get_js('header', array($expected['js'] => drupal_js_defaults($expected['js'])), TRUE);
+    $this->assertEqual($new_js, $original_js + array($expected['js'] => 1), t('Page state now has the %js file.', array('%js' => $expected['js'])));
+    $this->assertCommand($commands, array('data' => $expected_js_html), t('Page now has the %js file.', array('%js' => $expected['js'])));
+  }
 }
 
 /**
diff --git a/modules/simpletest/tests/ajax_forms_test.module b/modules/simpletest/tests/ajax_forms_test.module
index d38cbbb9053d..075b005eaeea 100644
--- a/modules/simpletest/tests/ajax_forms_test.module
+++ b/modules/simpletest/tests/ajax_forms_test.module
@@ -29,6 +29,12 @@ function ajax_forms_test_menu() {
     'page arguments' => array('ajax_forms_test_validation_form'),
     'access callback' => TRUE,
   );
+  $items['ajax_forms_test_lazy_load_form'] = array(
+    'title' => 'AJAX forms lazy load test',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('ajax_forms_test_lazy_load_form'),
+    'access callback' => TRUE,
+  );
   return $items;
 }
 
@@ -457,3 +463,38 @@ function ajax_forms_test_validation_form_callback($form, $form_state) {
   drupal_set_message(t("Callback: drivertext=%drivertext, spare_required_field=%spare_required_field", array('%drivertext' => $form_state['values']['drivertext'], '%spare_required_field' => $form_state['values']['spare_required_field'])));
   return '<div id="message_area">ajax_forms_test_validation_form_callback at ' . date('c') . '</div>';
 }
+
+/**
+ * Form builder: Builds a form that triggers a simple AJAX callback.
+ */
+function ajax_forms_test_lazy_load_form($form, &$form_state) {
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Submit'),
+    '#ajax' => array(
+      'callback' => 'ajax_forms_test_lazy_load_form_ajax',
+    ),
+  );
+  return $form;
+}
+
+/**
+ * Form submit handler: Adds JavaScript and CSS that wasn't on the original form.
+ */
+function ajax_forms_test_lazy_load_form_submit($form, &$form_state) {
+  drupal_add_js(array('ajax_forms_test_lazy_load_form_submit' => 'executed'), 'setting');
+  drupal_add_css(drupal_get_path('module', 'system') . '/system.admin.css');
+  drupal_add_js(drupal_get_path('module', 'system') . '/system.js');
+  $form_state['rebuild'] = TRUE;
+}
+
+/**
+ * AJAX callback for the ajax_forms_test_lazy_load_form() form.
+ *
+ * This function returns nothing, because all we're interested in testing is
+ * ajax_render() adding commands for JavaScript and CSS added during the page
+ * request, such as the ones added in ajax_forms_test_lazy_load_form_submit().
+ */
+function ajax_forms_test_lazy_load_form_ajax($form, &$form_state) {
+  return NULL;
+}
-- 
GitLab