diff --git a/includes/ajax.inc b/includes/ajax.inc index ac1bd4c..80414ef 100644 --- a/includes/ajax.inc +++ b/includes/ajax.inc @@ -247,19 +247,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); @@ -278,11 +272,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 40af458..081b1b9 100644 --- a/modules/simpletest/drupal_web_test_case.php +++ b/modules/simpletest/drupal_web_test_case.php @@ -1963,6 +1963,16 @@ class DrupalWebTestCase extends DrupalTestCase { $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'. @@ -1987,7 +1997,7 @@ class DrupalWebTestCase extends DrupalTestCase { 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 9579792..9a76b96 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 d38cbbb..075b005 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 '
ajax_forms_test_validation_form_callback at ' . date('c') . '
'; } + +/** + * 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; +}