summaryrefslogtreecommitdiffstats
path: root/core/modules/system/lib/Drupal/system/Tests/Ajax/FrameworkTest.php
blob: b70a1e655784e72bf05bf75f56d976d4075bc4ff (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
<?php

/**
 * @file
 * Contains \Drupal\system\Tests\Ajax\FrameworkTest.
 */

namespace Drupal\system\Tests\Ajax;

use Drupal\Core\Ajax\AddCssCommand;
use Drupal\Core\Ajax\AlertCommand;
use Drupal\Core\Ajax\AppendCommand;
use Drupal\Core\Ajax\HtmlCommand;
use Drupal\Core\Ajax\PrependCommand;
use Drupal\Core\Ajax\SettingsCommand;

/**
 * Tests primary Ajax framework functions.
 */
class FrameworkTest extends AjaxTestBase {
  public static function getInfo() {
    return array(
      'name' => 'AJAX framework',
      'description' => 'Performs tests on AJAX framework functions.',
      'group' => 'AJAX',
    );
  }

  /**
   * Ensures ajax_render() returns JavaScript settings from the page request.
   */
  public function testAJAXRender() {
    // Verify that settings command is generated when JavaScript settings are
    // set via _drupal_add_js().
    $commands = $this->drupalGetAJAX('ajax-test/render');
    $expected = new SettingsCommand(array('ajax' => 'test'), TRUE);
    $this->assertCommand($commands, $expected->render(), 'ajax_render() loads settings added with _drupal_add_js().');
  }

  /**
   * Tests AjaxResponse::prepare() AJAX commands ordering.
   */
  public function testOrder() {
    $path = drupal_get_path('module', 'system');
    $expected_commands = array();

    // Expected commands, in a very specific order.
    $expected_commands[0] = new SettingsCommand(array('ajax' => 'test'), TRUE);
    drupal_static_reset('_drupal_add_css');
    $attached = array(
      '#attached' => array(
        'css' => array(
          $path . '/css/system.admin.css' => array(),
          $path . '/css/system.maintenance.css' => array()
        ),
      ),
    );
    drupal_render($attached);
    $expected_commands[1] = new AddCssCommand(drupal_get_css(_drupal_add_css(), TRUE));
    drupal_static_reset('_drupal_add_js');
    $attached = array(
      '#attached' => array(
        'js' => array(
          $path . '/system.js' => array(),
        ),
      ),
    );
    drupal_render($attached);
    $expected_commands[2] = new PrependCommand('head', drupal_get_js('header', _drupal_add_js(), TRUE));
    drupal_static_reset('_drupal_add_js');
    $attached = array(
      '#attached' => array(
        'js' => array(
          $path . '/system.modules.js' => array('scope' => 'footer'),
        ),
      ),
    );
    drupal_render($attached);
    $expected_commands[3] = new AppendCommand('body', drupal_get_js('footer', _drupal_add_js(), TRUE));
    $expected_commands[4] = new HtmlCommand('body', '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
    // drupalPostAjaxForm(), 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->drupalPostAjaxForm(NULL, array(), NULL, 'ajax-test/order', array(), array(), NULL, array());
    $this->assertCommand(array_slice($commands, 0, 1), $expected_commands[0]->render(), 'Settings command is first.');
    $this->assertCommand(array_slice($commands, 1, 1), $expected_commands[1]->render(), 'CSS command is second (and CSS files are ordered correctly).');
    $this->assertCommand(array_slice($commands, 2, 1), $expected_commands[2]->render(), 'Header JS command is third.');
    $this->assertCommand(array_slice($commands, 3, 1), $expected_commands[3]->render(), 'Footer JS command is fourth.');
    $this->assertCommand(array_slice($commands, 4, 1), $expected_commands[4]->render(), 'HTML command is fifth.');
  }

  /**
   * Tests behavior of ajax_render_error().
   */
  public function testAJAXRenderError() {
    // Verify custom error message.
    $edit = array(
      'message' => 'Custom error message.',
    );
    $commands = $this->drupalGetAJAX('ajax-test/render-error', array('query' => $edit));
    $expected = new AlertCommand($edit['message']);
    $this->assertCommand($commands, $expected->render(), 'Custom error message is output.');
  }

  /**
   * Tests that new JavaScript and CSS files are lazy-loaded on an AJAX request.
   */
  public function testLazyLoad() {
    $expected = array(
      'setting_name' => 'ajax_forms_test_lazy_load_form_submit',
      'setting_value' => 'executed',
      'css' => drupal_get_path('module', 'system') . '/css/system.admin.css',
      'js' => drupal_get_path('module', 'system') . '/system.js',
    );
    // CSS files are stored by basename, see _drupal_add_css().
    $expected_css_basename = drupal_basename($expected['css']);

    // @todo D8: Add a drupal_css_defaults() helper function.
    $expected_css_html = drupal_get_css(array($expected_css_basename => array(
      'type' => 'file',
      'group' => CSS_AGGREGATE_DEFAULT,
      'weight' => 0,
      'every_page' => FALSE,
      'media' => 'all',
      'preprocess' => TRUE,
      'data' => $expected['css'],
      'browsers' => array('IE' => TRUE, '!IE' => TRUE),
    )), TRUE);
    $expected_js_html = drupal_get_js('header', array($expected['js'] => drupal_js_defaults($expected['js'])), TRUE);

    // 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 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_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.
    $commands = $this->drupalPostAjaxForm(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[$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;
    foreach ($commands as $command) {
      if ($command['command'] == 'settings' && (array_key_exists('css', $command['settings']['ajaxPageState']) || array_key_exists('js', $command['settings']['ajaxPageState']))) {
        $found_settings_command = TRUE;
      }
      if (isset($command['data']) && ($command['data'] == $expected_js_html || $command['data'] == $expected_css_html)) {
        $found_markup_command = TRUE;
      }
    }
    $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.
    $commands = $this->drupalPostAjaxForm(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 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'])));
    $expected_command = new SettingsCommand(array($expected['setting_name'] => $expected['setting_value']), TRUE);
    $this->assertCommand(array_slice($commands, 0, 1), $expected_command->render(), format_string('The settings command was first.'));

    // 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 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
    // accidentally get added.
    $this->assertEqual($new_js, $original_js + array($expected['js'] => 1), format_string('Page state now has the %js file.', array('%js' => $expected['js'])));
    $this->assertCommand(array_slice($commands, 2, 1), array('data' => $expected_js_html), format_string('Page now has the %js file.', array('%js' => $expected['js'])));
  }

  /**
   * Tests that drupalSettings.currentPath is not updated on AJAX requests.
   */
  public function testCurrentPathChange() {
    $commands = $this->drupalPostAjaxForm('ajax_forms_test_lazy_load_form', array('add_files' => FALSE), array('op' => t('Submit')));
    foreach ($commands as $command) {
      if ($command['command'] == 'settings') {
        $this->assertFalse(isset($command['settings']['currentPath']), 'Value of drupalSettings.currentPath is not updated after an AJAX request.');
      }
    }
  }

  /**
   * Tests that overridden CSS files are not added during lazy load.
   */
  public function testLazyLoadOverriddenCSS() {
    // The test theme overrides system.module.css without an implementation,
    // thereby removing it.
    theme_enable(array('test_theme'));
    \Drupal::config('system.theme')
      ->set('default', 'test_theme')
      ->save();

    // This gets the form, and emulates an Ajax submission on it, including
    // adding markup to the HEAD and BODY for any lazy loaded JS/CSS files.
    $this->drupalPostAjaxForm('ajax_forms_test_lazy_load_form', array('add_files' => TRUE), array('op' => t('Submit')));

    // Verify that the resulting HTML does not load the overridden CSS file.
    // We add a "?" to the assertion, because drupalSettings may include
    // information about the file; we only really care about whether it appears
    // in a LINK or STYLE tag, for which Drupal always adds a query string for
    // cache control.
    $this->assertNoText('system.module.css?', 'Ajax lazy loading does not add overridden CSS files.');
  }
}