core/lib/Drupal/Core/Ajax/AjaxResponse.php | 4 +- .../lib/Drupal/system/Tests/Ajax/FrameworkTest.php | 100 ++++++++++++++++---- .../tests/modules/ajax_test/ajax_test.module | 33 ++++++- 3 files changed, 117 insertions(+), 20 deletions(-) diff --git a/core/lib/Drupal/Core/Ajax/AjaxResponse.php b/core/lib/Drupal/Core/Ajax/AjaxResponse.php index 83124e8..154d77a 100644 --- a/core/lib/Drupal/Core/Ajax/AjaxResponse.php +++ b/core/lib/Drupal/Core/Ajax/AjaxResponse.php @@ -51,8 +51,8 @@ public function addCommand($command, $prepend = FALSE) { * @param Request $request * A request object. * - * @return - * Response The current response. + * @return Response + * The current response. */ public function prepare(Request $request) { $this->setData($this->ajaxRender($request)); diff --git a/core/modules/system/lib/Drupal/system/Tests/Ajax/FrameworkTest.php b/core/modules/system/lib/Drupal/system/Tests/Ajax/FrameworkTest.php index ad9ddbd..51e88b7 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Ajax/FrameworkTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Ajax/FrameworkTest.php @@ -23,17 +23,16 @@ public static function getInfo() { * Ensures ajax_render() returns JavaScript settings from the page request. */ function testAJAXRender() { + // Verify that settings command is generated when JavaScript settings are + // set via drupal_add_js(). $commands = $this->drupalGetAJAX('ajax-test/render'); - - // Verify that there is a command to load settings added with - // drupal_add_js(). $expected = array( 'command' => 'settings', 'settings' => array('ajax' => 'test'), ); $this->assertCommand($commands, $expected, 'ajax_render() loads settings added with drupal_add_js().'); - // Verify that Ajax settings are loaded for #type 'link'. + // Verify that JavaScript settings are loaded for #type 'link'. $this->drupalGet('ajax-test/link'); $settings = $this->drupalGetSettings(); $this->assertEqual($settings['ajax']['ajax-link']['url'], url('filter/tips')); @@ -41,6 +40,69 @@ function testAJAXRender() { } /** + * Tests AjaxResponse::prepare() AJAX commands ordering. + */ + function testOrder() { + $path = drupal_get_path('module', 'system'); + $expected_commands = array(); + + // Expected commands, in a very specific order. + $expected_commands[0] = array( + 'command' => 'settings', + 'settings' => array('ajax' => 'test'), + ); + drupal_static_reset('drupal_add_css'); + drupal_add_css($path . '/system.admin.css'); + drupal_add_css($path . '/system.maintenance.css'); + $expected_commands[1] = array( + 'command' => 'add_css', + 'data' => drupal_get_css(drupal_add_css(), TRUE), + ); + drupal_static_reset('drupal_add_js'); + drupal_add_js($path . '/system.js'); + $expected_commands[2] = array( + 'command' => 'insert', + 'method' => 'prepend', + 'selector' => 'head', + 'data' => drupal_get_js('header', drupal_add_js(), TRUE), + ); + drupal_static_reset('drupal_add_js'); + drupal_add_js($path . '/system.modules.js', array('scope' => 'footer')); + $expected_commands[3] = array( + 'command' => 'insert', + 'method' => 'append', + 'selector' => 'body', + 'data' => drupal_get_js('footer', drupal_add_js(), TRUE), + ); + $expected_commands[4] = array( + 'command' => 'insert', + 'method' => 'html', + 'selector' => 'body', + 'data' => 'Hello, world!', + ); + + // Load any page with at least one CSS file, at least one JavaScript file + // and at least one #ajax-powered element. The latter is an assumption of + // drupalPostAJAX(), the two former are assumptions of + // AjaxReponse::ajaxRender(). + // @todo refactor AJAX Framework + tests to make less assumptions. + $this->drupalGet('ajax_forms_test_lazy_load_form'); + + // Verify AJAX command order — this should always be the order: + // 1. JavaScript settings + // 2. CSS files + // 3. JavaScript files in the header + // 4. JavaScript files in the footer + // 5. Any other AJAX commands, in whatever order they were added. + $commands = $this->drupalPostAJAX(NULL, array(), NULL, 'ajax-test/order', array(), array(), NULL, array()); + $this->assertCommand(array_slice($commands, 0, 1), $expected_commands[0], 'Settings command is first.'); + $this->assertCommand(array_slice($commands, 1, 1), $expected_commands[1], 'CSS command is second (and CSS files are ordered correctly).'); + $this->assertCommand(array_slice($commands, 2, 1), $expected_commands[2], 'Header JS command is third.'); + $this->assertCommand(array_slice($commands, 3, 1), $expected_commands[3], 'Footer JS command is fourth.'); + $this->assertCommand(array_slice($commands, 4, 1), $expected_commands[4], 'HTML command is fifth.'); + } + + /** * Tests behavior of ajax_render_error(). */ function testAJAXRenderError() { @@ -65,7 +127,7 @@ function testAJAXRenderError() { } /** - * Tests that new JavaScript and CSS files are returned on an Ajax request. + * Tests that new JavaScript and CSS files are lazy-loaded on an AJAX request. */ function testLazyLoad() { $expected = array( @@ -99,16 +161,20 @@ function testLazyLoad() { // Verify that the base page doesn't have the settings and files that are to // be lazy loaded as part of the next requests. $this->assertTrue(!isset($original_settings[$expected['setting_name']]), format_string('Page originally lacks the %setting, as expected.', array('%setting' => $expected['setting_name']))); - $this->assertTrue(!isset($original_settings[$expected['css']]), format_string('Page originally lacks the %css file, as expected.', array('%css' => $expected['css']))); - $this->assertTrue(!isset($original_settings[$expected['js']]), format_string('Page originally lacks the %js file, as expected.', array('%js' => $expected['js']))); + $this->assertTrue(!isset($original_css[$expected['css']]), format_string('Page originally lacks the %css file, as expected.', array('%css' => $expected['css']))); + $this->assertTrue(!isset($original_js[$expected['js']]), format_string('Page originally lacks the %js file, as expected.', array('%js' => $expected['js']))); - // Submit the Ajax request without triggering files getting added. + // Submit the AJAX request without triggering files getting added. $commands = $this->drupalPostAJAX(NULL, array('add_files' => FALSE), array('op' => t('Submit'))); $new_settings = $this->drupalGetSettings(); + $new_css = $new_settings['ajaxPageState']['css']; + $new_js = $new_settings['ajaxPageState']['js']; // Verify the setting was not added when not expected. - $this->assertTrue(!isset($new_settings['setting_name']), format_string('Page still lacks the %setting, as expected.', array('%setting' => $expected['setting_name']))); - // Verify a settings command does not add CSS or scripts to Drupal.settings + $this->assertTrue(!isset($new_settings[$expected['setting_name']]), format_string('Page still lacks the %setting, as expected.', array('%setting' => $expected['setting_name']))); + $this->assertTrue(!isset($new_css[$expected['css']]), format_string('Page still lacks the %css file, as expected.', array('%css' => $expected['css']))); + $this->assertTrue(!isset($new_js[$expected['js']]), format_string('Page still lacks the %js file, as expected.', array('%js' => $expected['js']))); + // Verify a settings command does not add CSS or scripts to drupalSettings // and no command inserts the corresponding tags on the page. $found_settings_command = FALSE; $found_markup_command = FALSE; @@ -123,24 +189,24 @@ function testLazyLoad() { $this->assertFalse($found_settings_command, format_string('Page state still lacks the %css and %js files, as expected.', array('%css' => $expected['css'], '%js' => $expected['js']))); $this->assertFalse($found_markup_command, format_string('Page still lacks the %css and %js files, as expected.', array('%css' => $expected['css'], '%js' => $expected['js']))); - // Submit the Ajax request and trigger adding files. + // Submit the AJAX request and trigger adding files. $commands = $this->drupalPostAJAX(NULL, array('add_files' => TRUE), 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, both to Drupal.settings, and as - // the first Ajax command. + // Verify the expected setting was added, both to drupalSettings, and as + // the first AJAX command. $this->assertIdentical($new_settings[$expected['setting_name']], $expected['setting_value'], format_string('Page now has the %setting.', array('%setting' => $expected['setting_name']))); $this->assertCommand(array_slice($commands, 0, 1), array('settings' => array($expected['setting_name'] => $expected['setting_value'])), format_string('The settings command was first.')); - // Verify the expected CSS file was added, both to Drupal.settings, and as - // the second Ajax command for inclusion into the HTML. + // Verify the expected CSS file was added, both to drupalSettings, and as + // the second AJAX command for inclusion into the HTML. $this->assertEqual($new_css, $original_css + array($expected_css_basename => 1), format_string('Page state now has the %css file.', array('%css' => $expected['css']))); $this->assertCommand(array_slice($commands, 1, 1), array('data' => $expected_css_html), format_string('Page now has the %css file.', array('%css' => $expected['css']))); - // Verify the expected JS file was added, both to Drupal.settings, and as - // the third Ajax command for inclusion into the HTML. By testing for an + // Verify the expected JS file was added, both to drupalSettings, and as + // the third 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 diff --git a/core/modules/system/tests/modules/ajax_test/ajax_test.module b/core/modules/system/tests/modules/ajax_test/ajax_test.module index efb1966..5af431c 100644 --- a/core/modules/system/tests/modules/ajax_test/ajax_test.module +++ b/core/modules/system/tests/modules/ajax_test/ajax_test.module @@ -5,6 +5,9 @@ * Helper module for Ajax framework tests. */ +use Drupal\Core\Ajax\AjaxResponse; +use Drupal\Core\Ajax\HtmlCommand; + /** * Implements hook_menu(). */ @@ -15,6 +18,13 @@ function ajax_test_menu() { 'access callback' => TRUE, 'type' => MENU_CALLBACK, ); + $items['ajax-test/order'] = array( + 'title' => 'AJAX commands order', + 'page callback' => 'ajax_test_order', + 'theme callback' => 'ajax_base_page_theme', + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, + ); $items['ajax-test/render-error'] = array( 'title' => 'ajax_render_error', 'page callback' => 'ajax_test_error', @@ -48,7 +58,7 @@ function ajax_test_system_theme_info() { } /** - * Page callback: Returns an element suitable for use by ajax_render(). + * Menu callback: Returns an element suitable for use by ajax_render(). * * Additionally ensures that ajax_render() incorporates JavaScript settings * generated during the page request by invoking drupal_add_js() with a dummy @@ -60,6 +70,27 @@ function ajax_test_render() { } /** + * Menu callback: Returns an AjaxResponse; settings command set last. + * + * Helps verifying AjaxResponse reorders commands to ensure correct execution. + */ +function ajax_test_order() { + $response = new AjaxResponse(); + $path = drupal_get_path('module', 'system'); + // Add two CSS files (order should remain the same). + drupal_add_css($path . '/system.admin.css'); + drupal_add_css($path . '/system.maintenance.css'); + // Add two JavaScript files (first to the footer, should appear last). + drupal_add_js($path . '/system.modules.js', array('scope' => 'footer')); + drupal_add_js($path . '/system.js'); + // HTML insertion command. + $response->addCommand(new HtmlCommand('body', 'Hello, world!')); + // Finally, add a JavaScript setting. + drupal_add_js(array('ajax' => 'test'), 'setting'); + return $response; +} + +/** * Menu callback: Returns AJAX element with #error property set. */ function ajax_test_error() {