QuickEditLoadingTest.php 27.7 KB
Newer Older
1
2
3
4
<?php

/**
 * @file
5
 * Contains \Drupal\quickedit\Tests\QuickEditLoadingTest.
6
7
 */

8
namespace Drupal\quickedit\Tests;
9

10
use Drupal\Component\Serialization\Json;
11
use Drupal\Component\Utility\Unicode;
12
use Drupal\block_content\Entity\BlockContent;
13
14
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
15
16
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Core\Url;
17
use Drupal\node\Entity\Node;
18
use Drupal\node\Entity\NodeType;
19
use Drupal\simpletest\WebTestBase;
20
21

/**
22
23
24
25
 * Tests loading of in-place editing functionality and lazy loading of its
 * in-place editors.
 *
 * @group quickedit
26
 */
27
class QuickEditLoadingTest extends WebTestBase {
28
29
30
31
32
33

  /**
   * Modules to enable.
   *
   * @var array
   */
34
35
36
37
38
39
40
  public static $modules = array(
    'contextual',
    'quickedit',
    'filter',
    'node',
    'image',
  );
41

42
43
44
45
46
47
48
49
50
51
52
53
54
55
  /**
   * An user with permissions to create and edit articles.
   *
   * @var \Drupal\user\UserInterface
   */
  protected $authorUser;

  /**
   * A author user with permissions to access in-place editor.
   *
   * @var \Drupal\user\UserInterface
   */
  protected $editorUser;

56
  protected function setUp() {
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
    parent::setUp();

    // Create a text format.
    $filtered_html_format = entity_create('filter_format', array(
      'format' => 'filtered_html',
      'name' => 'Filtered HTML',
      'weight' => 0,
      'filters' => array(),
    ));
    $filtered_html_format->save();

    // Create a node type.
    $this->drupalCreateContentType(array(
      'type' => 'article',
      'name' => 'Article',
    ));

    // Create one node of the above node type using the above text format.
    $this->drupalCreateNode(array(
      'type' => 'article',
      'body' => array(
        0 => array(
          'value' => '<p>How are you?</p>',
          'format' => 'filtered_html',
        )
82
      ),
83
      'revision_log' => $this->randomString(),
84
85
86
87
88
    ));

    // Create 2 users, the only difference being the ability to use in-place
    // editing
    $basic_permissions = array('access content', 'create article content', 'edit any article content', 'use text format filtered_html', 'access contextual links');
89
90
    $this->authorUser = $this->drupalCreateUser($basic_permissions);
    $this->editorUser = $this->drupalCreateUser(array_merge($basic_permissions, array('access in-place editing')));
91
92
93
  }

  /**
94
   * Test the loading of Quick Edit when a user doesn't have access to it.
95
   */
96
  public function testUserWithoutPermission() {
97
    $this->drupalLogin($this->authorUser);
98
99
    $this->drupalGet('node/1');

100
    // Library and in-place editors.
101
102
    $this->assertNoRaw('core/modules/quickedit/js/quickedit.js',  'Quick Edit library not loaded.');
    $this->assertNoRaw('core/modules/quickedit/js/editors/formEditor.js', "'form' in-place editor not loaded.");
103
104

    // HTML annotation must always exist (to not break the render cache).
105
    $this->assertRaw('data-quickedit-entity-id="node/1"');
106
    $this->assertRaw('data-quickedit-field-id="node/1/body/en/full"');
107
108

    // Retrieving the metadata should result in an empty 403 response.
109
    $post = array('fields[0]' => 'node/1/body/en/full');
110
    $response = $this->drupalPostWithFormat(Url::fromRoute('quickedit.metadata'), 'json', $post);
111
    $this->assertIdentical('{"message":""}', $response);
112
    $this->assertResponse(403);
113

114
    // Quick Edit's JavaScript would SearchRankingTestnever hit these endpoints if the metadata
115
116
    // was empty as above, but we need to make sure that malicious users aren't
    // able to use any of the other endpoints either.
117
    $post = array('editors[0]' => 'form') + $this->getAjaxPageStatePostData();
118
    $response = $this->drupalPost('quickedit/attachments', '', $post, ['query' => [MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_ajax']]);
119
    $this->assertIdentical('{}', $response);
120
    $this->assertResponse(403);
121
    $post = array('nocssjs' => 'true') + $this->getAjaxPageStatePostData();
122
    $response = $this->drupalPost('quickedit/form/' . 'node/1/body/en/full', '', $post, ['query' => [MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_ajax']]);
123
    $this->assertIdentical('{}', $response);
124
125
    $this->assertResponse(403);
    $edit = array();
126
    $edit['form_id'] = 'quickedit_field_form';
127
128
    $edit['form_token'] = 'xIOzMjuc-PULKsRn_KxFn7xzNk5Bx7XKXLfQfw1qOnA';
    $edit['form_build_id'] = 'form-kVmovBpyX-SJfTT5kY0pjTV35TV-znor--a64dEnMR8';
129
130
131
    $edit['body[0][summary]'] = '';
    $edit['body[0][value]'] = '<p>Malicious content.</p>';
    $edit['body[0][format]'] = 'filtered_html';
132
    $edit['op'] = t('Save');
133
    $response = $this->drupalPost('quickedit/form/' . 'node/1/body/en/full', '', $edit, ['query' => [MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_ajax']]);
134
    $this->assertIdentical('{}', $response);
135
    $this->assertResponse(403);
136
    $post = array('nocssjs' => 'true');
137
    $response = $this->drupalPostWithFormat('quickedit/entity/' . 'node/1', 'json', $post);
138
    $this->assertIdentical('{"message":""}', $response);
139
    $this->assertResponse(403);
140
141
142
  }

  /**
143
   * Tests the loading of Quick Edit when a user does have access to it.
144
145
146
   *
   * Also ensures lazy loading of in-place editors works.
   */
147
  public function testUserWithPermission() {
148
    $this->drupalLogin($this->editorUser);
149
150
    $this->drupalGet('node/1');

151
    // Library and in-place editors.
152
    $settings = $this->getDrupalSettings();
153
154
155
    $libraries = explode(',', $settings['ajaxPageState']['libraries']);
    $this->assertTrue(in_array('quickedit/quickedit', $libraries), 'Quick Edit library loaded.');
    $this->assertFalse(in_array('quickedit/quickedit.inPlaceEditor.form', $libraries), "'form' in-place editor not loaded.");
156
157

    // HTML annotation must always exist (to not break the render cache).
158
    $this->assertRaw('data-quickedit-entity-id="node/1"');
159
    $this->assertRaw('data-quickedit-field-id="node/1/body/en/full"');
160

161
    // There should be only one revision so far.
162
    $node = Node::load(1);
163
164
    $vids = \Drupal::entityManager()->getStorage('node')->revisionIds($node);
    $this->assertIdentical(1, count($vids), 'The node has only one revision.');
165
    $original_log = $node->revision_log->value;
166

167
168
    // Retrieving the metadata should result in a 200 JSON response.
    $htmlPageDrupalSettings = $this->drupalSettings;
169
    $post = array('fields[0]' => 'node/1/body/en/full');
170
    $response = $this->drupalPostWithFormat('quickedit/metadata', 'json', $post);
171
    $this->assertResponse(200);
172
    $expected = array(
173
      'node/1/body/en/full' => array(
174
175
176
177
178
        'label' => 'Body',
        'access' => TRUE,
        'editor' => 'form',
      )
    );
179
    $this->assertIdentical(Json::decode($response), $expected, 'The metadata HTTP request answers with the correct JSON response.');
180
181
182
    // Restore drupalSettings to build the next requests; simpletest wipes them
    // after a JSON response.
    $this->drupalSettings = $htmlPageDrupalSettings;
183

184
185
186
    // Retrieving the attachments should result in a 200 response, containing:
    //  1. a settings command with useless metadata: AjaxController is dumb
    //  2. an insert command that loads the required in-place editors
187
    $post = array('editors[0]' => 'form') + $this->getAjaxPageStatePostData();
188
    $response = $this->drupalPost('quickedit/attachments', 'application/vnd.drupal-ajax', $post);
189
    $ajax_commands = Json::decode($response);
190
    $this->assertIdentical(2, count($ajax_commands), 'The attachments HTTP request results in two AJAX commands.');
191
192
193
194
    // First command: settings.
    $this->assertIdentical('settings', $ajax_commands[0]['command'], 'The first AJAX command is a settings command.');
    // Second command: insert libraries into DOM.
    $this->assertIdentical('insert', $ajax_commands[1]['command'], 'The second AJAX command is an append command.');
195
    $this->assertTrue(in_array('quickedit/quickedit.inPlaceEditor.form', explode(',', $ajax_commands[0]['settings']['ajaxPageState']['libraries'])), 'The quickedit.inPlaceEditor.form library is loaded.');
196

197
    // Retrieving the form for this field should result in a 200 response,
198
    // containing only a quickeditFieldForm command.
199
    $post = array('nocssjs' => 'true', 'reset' => 'true') + $this->getAjaxPageStatePostData();
200
    $response = $this->drupalPost('quickedit/form/' . 'node/1/body/en/full', 'application/vnd.drupal-ajax', $post);
201
    $this->assertResponse(200);
202
    $ajax_commands = Json::decode($response);
203
    $this->assertIdentical(1, count($ajax_commands), 'The field form HTTP request results in one AJAX command.');
204
205
    $this->assertIdentical('quickeditFieldForm', $ajax_commands[0]['command'], 'The first AJAX command is a quickeditFieldForm command.');
    $this->assertIdentical('<form ', Unicode::substr($ajax_commands[0]['data'], 0, 6), 'The quickeditFieldForm command contains a form.');
206

207
    // Prepare form values for submission. drupalPostAjaxForm() is not suitable
208
    // for handling pages with JSON responses, so we need our own solution here.
209
210
211
212
    $form_tokens_found = preg_match('/\sname="form_token" value="([^"]+)"/', $ajax_commands[0]['data'], $token_match) && preg_match('/\sname="form_build_id" value="([^"]+)"/', $ajax_commands[0]['data'], $build_id_match);
    $this->assertTrue($form_tokens_found, 'Form tokens found in output.');

    if ($form_tokens_found) {
213
      $edit = array(
214
215
216
        'body[0][summary]' => '',
        'body[0][value]' => '<p>Fine thanks.</p>',
        'body[0][format]' => 'filtered_html',
217
218
        'op' => t('Save'),
      );
219
      $post = array(
220
        'form_id' => 'quickedit_field_form',
221
222
223
224
        'form_token' => $token_match[1],
        'form_build_id' => $build_id_match[1],
      );
      $post += $edit + $this->getAjaxPageStatePostData();
225

226
      // Submit field form and check response. This should store the updated
227
      // entity in PrivateTempStore on the server.
228
      $response = $this->drupalPost('quickedit/form/' . 'node/1/body/en/full', 'application/vnd.drupal-ajax', $post);
229
      $this->assertResponse(200);
230
      $ajax_commands = Json::decode($response);
231
      $this->assertIdentical(1, count($ajax_commands), 'The field form HTTP request results in one AJAX command.');
232
      $this->assertIdentical('quickeditFieldFormSaved', $ajax_commands[0]['command'], 'The first AJAX command is a quickeditFieldFormSaved command.');
233
      $this->assertTrue(strpos($ajax_commands[0]['data'], 'Fine thanks.'), 'Form value saved and printed back.');
234
      $this->assertIdentical($ajax_commands[0]['other_view_modes'], array(), 'Field was not rendered in any other view mode.');
235
236
237
238
239

      // Ensure the text on the original node did not change yet.
      $this->drupalGet('node/1');
      $this->assertText('How are you?');

240
      // Save the entity by moving the PrivateTempStore values to entity storage.
241
      $post = array('nocssjs' => 'true');
242
      $response = $this->drupalPostWithFormat('quickedit/entity/' . 'node/1', 'json', $post);
243
      $this->assertResponse(200);
244
      $ajax_commands = Json::decode($response);
245
      $this->assertIdentical(1, count($ajax_commands), 'The entity submission HTTP request results in one AJAX command.');
246
      $this->assertIdentical('quickeditEntitySaved', $ajax_commands[0]['command'], 'The first AJAX command is a quickeditEntitySaved command.');
247
248
249
250
251
252
      $this->assertIdentical($ajax_commands[0]['data']['entity_type'], 'node', 'Saved entity is of type node.');
      $this->assertIdentical($ajax_commands[0]['data']['entity_id'], '1', 'Entity id is 1.');

      // Ensure the text on the original node did change.
      $this->drupalGet('node/1');
      $this->assertText('Fine thanks.');
253
254

      // Ensure no new revision was created and the log message is unchanged.
255
      $node = Node::load(1);
256
257
      $vids = \Drupal::entityManager()->getStorage('node')->revisionIds($node);
      $this->assertIdentical(1, count($vids), 'The node has only one revision.');
258
      $this->assertIdentical($original_log, $node->revision_log->value, 'The revision log message is unchanged.');
259
260
261

      // Now configure this node type to create new revisions automatically,
      // then again retrieve the field form, fill it, submit it (so it ends up
262
      // in PrivateTempStore) and then save the entity. Now there should be two
263
      // revisions.
264
      $node_type = NodeType::load('article');
265
      $node_type->setNewRevision(TRUE);
266
      $node_type->save();
267
268
269

      // Retrieve field form.
      $post = array('nocssjs' => 'true', 'reset' => 'true');
270
      $response = $this->drupalPost('quickedit/form/' . 'node/1/body/en/full', 'application/vnd.drupal-ajax', $post);
271
      $this->assertResponse(200);
272
      $ajax_commands = Json::decode($response);
273
      $this->assertIdentical(1, count($ajax_commands), 'The field form HTTP request results in one AJAX command.');
274
275
      $this->assertIdentical('quickeditFieldForm', $ajax_commands[0]['command'], 'The first AJAX command is a quickeditFieldForm command.');
      $this->assertIdentical('<form ', Unicode::substr($ajax_commands[0]['data'], 0, 6), 'The quickeditFieldForm command contains a form.');
276
277
278
279
280
281

      // Submit field form.
      preg_match('/\sname="form_token" value="([^"]+)"/', $ajax_commands[0]['data'], $token_match);
      preg_match('/\sname="form_build_id" value="([^"]+)"/', $ajax_commands[0]['data'], $build_id_match);
      $edit['body[0][value]'] = '<p>kthxbye</p>';
      $post = array(
282
        'form_id' => 'quickedit_field_form',
283
284
285
286
        'form_token' => $token_match[1],
        'form_build_id' => $build_id_match[1],
      );
      $post += $edit + $this->getAjaxPageStatePostData();
287
      $response = $this->drupalPost('quickedit/form/' . 'node/1/body/en/full', 'application/vnd.drupal-ajax', $post);
288
      $this->assertResponse(200);
289
      $ajax_commands = Json::decode($response);
290
      $this->assertIdentical(1, count($ajax_commands), 'The field form HTTP request results in one AJAX command.');
291
      $this->assertIdentical('quickeditFieldFormSaved', $ajax_commands[0]['command'], 'The first AJAX command is an quickeditFieldFormSaved command.');
292
293
294
295
      $this->assertTrue(strpos($ajax_commands[0]['data'], 'kthxbye'), 'Form value saved and printed back.');

      // Save the entity.
      $post = array('nocssjs' => 'true');
296
      $response = $this->drupalPostWithFormat('quickedit/entity/' . 'node/1', 'json', $post);
297
      $this->assertResponse(200);
298
299
300
301
      $ajax_commands = Json::decode($response);
      $this->assertIdentical(1, count($ajax_commands));
      $this->assertIdentical('quickeditEntitySaved', $ajax_commands[0]['command'], 'The first AJAX command is an quickeditEntitySaved command.');
      $this->assertEqual($ajax_commands[0]['data'], ['entity_type' => 'node', 'entity_id' => 1], 'Updated entity type and ID returned');
302
303

      // Test that a revision was created with the correct log message.
304
      $vids = \Drupal::entityManager()->getStorage('node')->revisionIds(Node::load(1));
305
306
      $this->assertIdentical(2, count($vids), 'The node has two revisions.');
      $revision = node_revision_load($vids[0]);
307
      $this->assertIdentical($original_log, $revision->revision_log->value, 'The first revision log message is unchanged.');
308
      $revision = node_revision_load($vids[1]);
309
      $this->assertIdentical('Updated the <em class="placeholder">Body</em> field through in-place editing.', $revision->revision_log->value, 'The second revision log message was correctly generated by Quick Edit module.');
310
    }
311
312
  }

313
  /**
314
   * Tests the loading of Quick Edit for the title base field.
315
316
   */
  public function testTitleBaseField() {
317
    $this->drupalLogin($this->editorUser);
318
319
    $this->drupalGet('node/1');

320
    // Ensure that the full page title is actually in-place editable
321
    $node = Node::load(1);
322
    $elements = $this->xpath('//h1/span[@data-quickedit-field-id="node/1/title/en/full" and normalize-space(text())=:title]', array(':title' => $node->label()));
323
324
    $this->assertTrue(!empty($elements), 'Title with data-quickedit-field-id attribute found.');

325
326
    // Retrieving the metadata should result in a 200 JSON response.
    $htmlPageDrupalSettings = $this->drupalSettings;
327
    $post = array('fields[0]' => 'node/1/title/en/full');
328
    $response = $this->drupalPostWithFormat('quickedit/metadata', 'json', $post);
329
330
    $this->assertResponse(200);
    $expected = array(
331
      'node/1/title/en/full' => array(
332
333
334
335
336
        'label' => 'Title',
        'access' => TRUE,
        'editor' => 'plain_text',
      )
    );
337
    $this->assertIdentical(Json::decode($response), $expected, 'The metadata HTTP request answers with the correct JSON response.');
338
339
340
341
342
    // Restore drupalSettings to build the next requests; simpletest wipes them
    // after a JSON response.
    $this->drupalSettings = $htmlPageDrupalSettings;

    // Retrieving the form for this field should result in a 200 response,
343
    // containing only a quickeditFieldForm command.
344
    $post = array('nocssjs' => 'true', 'reset' => 'true') + $this->getAjaxPageStatePostData();
345
    $response = $this->drupalPost('quickedit/form/' . 'node/1/title/en/full', 'application/vnd.drupal-ajax', $post);
346
    $this->assertResponse(200);
347
    $ajax_commands = Json::decode($response);
348
    $this->assertIdentical(1, count($ajax_commands), 'The field form HTTP request results in one AJAX command.');
349
350
    $this->assertIdentical('quickeditFieldForm', $ajax_commands[0]['command'], 'The first AJAX command is a quickeditFieldForm command.');
    $this->assertIdentical('<form ', Unicode::substr($ajax_commands[0]['data'], 0, 6), 'The quickeditFieldForm command contains a form.');
351
352
353
354
355
356
357
358
359
360
361
362
363

    // Prepare form values for submission. drupalPostAjaxForm() is not suitable
    // for handling pages with JSON responses, so we need our own solution
    // here.
    $form_tokens_found = preg_match('/\sname="form_token" value="([^"]+)"/', $ajax_commands[0]['data'], $token_match) && preg_match('/\sname="form_build_id" value="([^"]+)"/', $ajax_commands[0]['data'], $build_id_match);
    $this->assertTrue($form_tokens_found, 'Form tokens found in output.');

    if ($form_tokens_found) {
      $edit = array(
        'title[0][value]' => 'Obligatory question',
        'op' => t('Save'),
      );
      $post = array(
364
        'form_id' => 'quickedit_field_form',
365
366
367
368
369
370
        'form_token' => $token_match[1],
        'form_build_id' => $build_id_match[1],
      );
      $post += $edit + $this->getAjaxPageStatePostData();

      // Submit field form and check response. This should store the
371
      // updated entity in PrivateTempStore on the server.
372
      $response = $this->drupalPost('quickedit/form/' . 'node/1/title/en/full', 'application/vnd.drupal-ajax', $post);
373
      $this->assertResponse(200);
374
      $ajax_commands = Json::decode($response);
375
      $this->assertIdentical(1, count($ajax_commands), 'The field form HTTP request results in one AJAX command.');
376
      $this->assertIdentical('quickeditFieldFormSaved', $ajax_commands[0]['command'], 'The first AJAX command is a quickeditFieldFormSaved command.');
377
378
379
380
381
382
      $this->assertTrue(strpos($ajax_commands[0]['data'], 'Obligatory question'), 'Form value saved and printed back.');

      // Ensure the text on the original node did not change yet.
      $this->drupalGet('node/1');
      $this->assertNoText('Obligatory question');

383
      // Save the entity by moving the PrivateTempStore values to entity storage.
384
      $post = array('nocssjs' => 'true');
385
      $response = $this->drupalPostWithFormat('quickedit/entity/' . 'node/1', 'json', $post);
386
      $this->assertResponse(200);
387
      $ajax_commands = Json::decode($response);
388
      $this->assertIdentical(1, count($ajax_commands), 'The entity submission HTTP request results in one AJAX command.');
389
      $this->assertIdentical('quickeditEntitySaved', $ajax_commands[0]['command'], 'The first AJAX command is n quickeditEntitySaved command.');
390
391
392
393
394
395
396
397
398
      $this->assertIdentical($ajax_commands[0]['data']['entity_type'], 'node', 'Saved entity is of type node.');
      $this->assertIdentical($ajax_commands[0]['data']['entity_id'], '1', 'Entity id is 1.');

      // Ensure the text on the original node did change.
      $this->drupalGet('node/1');
      $this->assertText('Obligatory question');
    }
  }

399
  /**
400
401
   * Tests that Quick Edit doesn't make pseudo fields or computed fields
   * editable.
402
   */
403
  public function testPseudoFields() {
404
    \Drupal::service('module_installer')->install(array('quickedit_test'));
405

406
    $this->drupalLogin($this->authorUser);
407
408
409
    $this->drupalGet('node/1');

    // Check that the data- attribute is not added.
410
    $this->assertNoRaw('data-quickedit-field-id="node/1/quickedit_test_pseudo_field/en/default"');
411
412
  }

413
  /**
414
415
   * Tests that Quick Edit doesn't make fields rendered with display options
   * editable.
416
417
   */
  public function testDisplayOptions() {
418
    $node = Node::load('1');
419
420
421
    $display_settings = array(
      'label' => 'inline',
    );
422
    $build = $node->body->view($display_settings);
423
    $output = \Drupal::service('renderer')->renderRoot($build);
424
    $this->assertFalse(strpos($output, 'data-quickedit-field-id'), 'data-quickedit-field-id attribute not added when rendering field using dynamic display options.');
425
426
  }

427
  /**
428
   * Tests that Quick Edit works with custom render pipelines.
429
430
   */
  public function testCustomPipeline() {
431
    \Drupal::service('module_installer')->install(array('quickedit_test'));
432

433
    $custom_render_url = 'quickedit/form/node/1/body/en/quickedit_test-custom-render-data';
434
    $this->drupalLogin($this->editorUser);
435
436
437
438

    // Request editing to render results with the custom render pipeline.
    $post = array('nocssjs' => 'true') + $this->getAjaxPageStatePostData();
    $response = $this->drupalPost($custom_render_url, 'application/vnd.drupal-ajax', $post);
439
    $ajax_commands = Json::decode($response);
440
441
442
443
444
445
446
447

    // Prepare form values for submission. drupalPostAJAX() is not suitable for
    // handling pages with JSON responses, so we need our own solution here.
    $form_tokens_found = preg_match('/\sname="form_token" value="([^"]+)"/', $ajax_commands[0]['data'], $token_match) && preg_match('/\sname="form_build_id" value="([^"]+)"/', $ajax_commands[0]['data'], $build_id_match);
    $this->assertTrue($form_tokens_found, 'Form tokens found in output.');

    if ($form_tokens_found) {
      $post = array(
448
        'form_id' => 'quickedit_field_form',
449
450
451
452
453
454
455
        'form_token' => $token_match[1],
        'form_build_id' => $build_id_match[1],
        'body[0][summary]' => '',
        'body[0][value]' => '<p>Fine thanks.</p>',
        'body[0][format]' => 'filtered_html',
        'op' => t('Save'),
      );
456
457
458
      // Assume there is another field on this page, which doesn't use a custom
      // render pipeline, but the default one, and it uses the "full" view mode.
      $post += array('other_view_modes[]' => 'full');
459
460
461
462
463

      // Submit field form and check response. Should render with the custom
      // render pipeline.
      $response = $this->drupalPost($custom_render_url, 'application/vnd.drupal-ajax', $post);
      $this->assertResponse(200);
464
      $ajax_commands = Json::decode($response);
465
      $this->assertIdentical(1, count($ajax_commands), 'The field form HTTP request results in one AJAX command.');
466
      $this->assertIdentical('quickeditFieldFormSaved', $ajax_commands[0]['command'], 'The first AJAX command is a quickeditFieldFormSaved command.');
467
      $this->assertTrue(strpos($ajax_commands[0]['data'], 'Fine thanks.'), 'Form value saved and printed back.');
468
      $this->assertTrue(strpos($ajax_commands[0]['data'], '<div class="quickedit-test-wrapper">') !== FALSE, 'Custom render pipeline used to render the value.');
469
470
      $this->assertIdentical(array_keys($ajax_commands[0]['other_view_modes']), array('full'), 'Field was also rendered in the "full" view mode.');
      $this->assertTrue(strpos($ajax_commands[0]['other_view_modes']['full'], 'Fine thanks.'), '"full" version of field contains the form value.');
471
472
473
    }
  }

474
  /**
475
476
   * Tests Quick Edit on a node that was concurrently edited on the full node
   * form.
477
478
   */
  public function testConcurrentEdit() {
479
    $this->drupalLogin($this->editorUser);
480
481

    $post = array('nocssjs' => 'true') + $this->getAjaxPageStatePostData();
482
    $response = $this->drupalPost('quickedit/form/' . 'node/1/body/en/full', 'application/vnd.drupal-ajax', $post);
483
    $this->assertResponse(200);
484
    $ajax_commands = Json::decode($response);
485
486
487
488
489
490
491
492

    // Prepare form values for submission. drupalPostAJAX() is not suitable for
    // handling pages with JSON responses, so we need our own solution here.
    $form_tokens_found = preg_match('/\sname="form_token" value="([^"]+)"/', $ajax_commands[0]['data'], $token_match) && preg_match('/\sname="form_build_id" value="([^"]+)"/', $ajax_commands[0]['data'], $build_id_match);
    $this->assertTrue($form_tokens_found, 'Form tokens found in output.');

    if ($form_tokens_found) {
      $post = array(
493
        'nocssjs' => 'true',
494
        'form_id' => 'quickedit_field_form',
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
        'form_token' => $token_match[1],
        'form_build_id' => $build_id_match[1],
        'body[0][summary]' => '',
        'body[0][value]' => '<p>Fine thanks.</p>',
        'body[0][format]' => 'filtered_html',
        'op' => t('Save'),
      );

      // Save the node on the regular node edit form.
      $this->drupalPostForm('node/1/edit', array(), t('Save'));
      // Ensure different save timestamps for field editing.
      sleep(2);

      // Submit field form and check response. Should throw a validation error
      // because the node was changed in the meantime.
510
      $response = $this->drupalPost('quickedit/form/' . 'node/1/body/en/full', 'application/vnd.drupal-ajax', $post);
511
      $this->assertResponse(200);
512
      $ajax_commands = Json::decode($response);
513
      $this->assertIdentical(2, count($ajax_commands), 'The field form HTTP request results in two AJAX commands.');
514
      $this->assertIdentical('quickeditFieldFormValidationErrors', $ajax_commands[1]['command'], 'The second AJAX command is a quickeditFieldFormValidationErrors command.');
515
516
517
518
      $this->assertTrue(strpos($ajax_commands[1]['data'], t('The content has either been modified by another user, or you have already submitted modifications. As a result, your changes cannot be saved.')), 'Error message returned to user.');
    }
  }

519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
  /**
   * Tests that Quick Edit's data- attributes are present for content blocks.
   */
  public function testContentBlock() {
    \Drupal::service('module_installer')->install(array('block_content'));

    // Create and place a content_block block.
    $block = BlockContent::create([
      'info' => $this->randomMachineName(),
      'type' => 'basic',
      'langcode' => 'en',
    ]);
    $block->save();
    $this->drupalPlaceBlock('block_content:' . $block->uuid());

    // Check that the data- attribute is present.
    $this->drupalGet('');
    $this->assertRaw('data-quickedit-entity-id="block_content/1"');
  }
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577

  /**
   * Tests that Quick Edit can handle an image field.
   */
  public function testImageField() {
    // Add an image field to the content type.
    FieldStorageConfig::create([
      'field_name' => 'field_image',
      'type' => 'image',
      'entity_type' => 'node',
    ])->save();
    FieldConfig::create([
      'field_name' => 'field_image',
      'field_type' => 'image',
      'label' => t('Image'),
      'entity_type' => 'node',
      'bundle' => 'article',
    ])->save();
    entity_get_form_display('node', 'article', 'default')
      ->setComponent('field_image', [
        'type' => 'image_image',
      ])
      ->save();

    // Add an image to the node.
    $this->drupalLogin($this->editorUser);
    $image = $this->drupalGetTestFiles('image')[0];
    $this->drupalPostForm('node/1/edit', [
      'files[field_image_0]' => $image->uri,
    ], t('Upload'));
    $this->drupalPostForm(NULL, [
      'field_image[0][alt]' => 'Vivamus aliquet elit',
    ], t('Save'));

    // The image field form should load normally.
    $response = $this->drupalPost('quickedit/form/node/1/field_image/en/full', 'application/vnd.drupal-ajax', ['nocssjs' => 'true'] + $this->getAjaxPageStatePostData());
    $this->assertResponse(200);
    $ajax_commands = Json::decode($response);
    $this->assertIdentical('<form ', Unicode::substr($ajax_commands[0]['data'], 0, 6), 'The quickeditFieldForm command contains a form.');
  }
578
}