first commit
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
name: 'Quick Edit test'
|
||||
type: module
|
||||
description: 'Support module for the Quick Edit module tests.'
|
||||
core: 8.x
|
||||
package: Testing
|
||||
version: VERSION
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Helper module for the Quick Edit tests.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
|
||||
/**
|
||||
* Implements hook_quickedit_render_field().
|
||||
*/
|
||||
function quickedit_test_quickedit_render_field(EntityInterface $entity, $field_name, $view_mode_id, $langcode) {
|
||||
$entity = \Drupal::service('entity.repository')->getTranslationFromContext($entity, $langcode);
|
||||
return [
|
||||
'#prefix' => '<div class="quickedit-test-wrapper">',
|
||||
'field' => $entity->get($field_name)->view($view_mode_id),
|
||||
'#suffix' => '</div>',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_formatter_info_alter().
|
||||
*
|
||||
* @see quickedit_field_formatter_info_alter()
|
||||
* @see editor_field_formatter_info_alter()
|
||||
*/
|
||||
function quickedit_test_field_formatter_info_alter(&$info) {
|
||||
// Update \Drupal\text\Plugin\Field\FieldFormatter\TextDefaultFormatter's
|
||||
// annotation to indicate that it supports the 'wysiwyg' in-place editor
|
||||
// provided by this module.
|
||||
$info['text_default']['quickedit'] = ['editor' => 'wysiwyg'];
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\quickedit_test;
|
||||
|
||||
/**
|
||||
* @deprecated in drupal:8.4.0 and is removed from drupal:9.0.0.
|
||||
*/
|
||||
class MockEditEntityFieldAccessCheck extends MockQuickEditEntityFieldAccessCheck {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\quickedit_test;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\quickedit\Access\QuickEditEntityFieldAccessCheckInterface;
|
||||
|
||||
/**
|
||||
* Access check for in-place editing entity fields.
|
||||
*/
|
||||
class MockQuickEditEntityFieldAccessCheck implements QuickEditEntityFieldAccessCheckInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function accessEditEntityField(EntityInterface $entity, $field_name) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\quickedit_test\Plugin\InPlaceEditor;
|
||||
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\quickedit\Plugin\InPlaceEditorBase;
|
||||
|
||||
/**
|
||||
* Defines the 'wysiwyg' in-place editor.
|
||||
*
|
||||
* @InPlaceEditor(
|
||||
* id = "wysiwyg",
|
||||
* )
|
||||
*/
|
||||
class WysiwygEditor extends InPlaceEditorBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isCompatible(FieldItemListInterface $items) {
|
||||
$field_definition = $items->getFieldDefinition();
|
||||
|
||||
// This editor is incompatible with multivalued fields.
|
||||
if ($field_definition->getFieldStorageDefinition()->getCardinality() != 1) {
|
||||
return FALSE;
|
||||
}
|
||||
// This editor is compatible with formatted ("rich") text fields; but only
|
||||
// if there is a currently active text format and that text format is the
|
||||
// 'full_html' text format.
|
||||
return $items[0]->format === 'full_html';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMetadata(FieldItemListInterface $items) {
|
||||
$metadata['format'] = $items[0]->format;
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAttachments() {
|
||||
return [
|
||||
'library' => [
|
||||
'quickedit_test/not-existing-wysiwyg',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\quickedit\Functional;
|
||||
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests using a custom pipeline with Quick Edit.
|
||||
*
|
||||
* @group quickedit
|
||||
*/
|
||||
class QuickEditCustomPipelineTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'quickedit',
|
||||
'quickedit_test',
|
||||
'node',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests that Quick Edit works with custom render pipelines.
|
||||
*/
|
||||
public function testCustomPipeline() {
|
||||
// Create a node type.
|
||||
$this->drupalCreateContentType([
|
||||
'type' => 'article',
|
||||
'name' => 'Article',
|
||||
]);
|
||||
$node = $this->createNode(['type' => 'article']);
|
||||
$editor_user = $this->drupalCreateUser([
|
||||
'access content',
|
||||
'create article content',
|
||||
'edit any article content',
|
||||
'access in-place editing',
|
||||
]);
|
||||
$this->drupalLogin($editor_user);
|
||||
|
||||
$custom_render_url = $this->buildUrl('quickedit/form/node/' . $node->id() . '/body/en/quickedit_test-custom-render-data');
|
||||
|
||||
$client = $this->getHttpClient();
|
||||
$post = ['nocssjs' => 'true'];
|
||||
$response = $client->post($custom_render_url, [
|
||||
'body' => http_build_query($post),
|
||||
'query' => [MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_ajax'],
|
||||
'cookies' => $this->getSessionCookies(),
|
||||
'headers' => [
|
||||
'Accept' => 'application/json',
|
||||
'Content-Type' => 'application/x-www-form-urlencoded',
|
||||
],
|
||||
'http_errors' => FALSE,
|
||||
]);
|
||||
|
||||
$this->assertEquals(200, $response->getStatusCode());
|
||||
|
||||
$ajax_commands = Json::decode($response->getBody());
|
||||
// Request editing to render results with the custom render pipeline.
|
||||
|
||||
// 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.');
|
||||
|
||||
$post = [
|
||||
'form_id' => 'quickedit_field_form',
|
||||
'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'),
|
||||
];
|
||||
// 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 += ['other_view_modes[]' => 'full'];
|
||||
|
||||
// Submit field form and check response. Should render with the custom
|
||||
// render pipeline.
|
||||
$response = $client->post($custom_render_url, [
|
||||
'body' => http_build_query($post),
|
||||
'query' => [MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_ajax'],
|
||||
'cookies' => $this->getSessionCookies(),
|
||||
'headers' => [
|
||||
'Accept' => 'application/json',
|
||||
'Content-Type' => 'application/x-www-form-urlencoded',
|
||||
],
|
||||
'http_errors' => FALSE,
|
||||
]);
|
||||
$ajax_commands = Json::decode($response->getBody());
|
||||
$this->assertCount(1, $ajax_commands, 'The field form HTTP request results in one AJAX command.');
|
||||
$this->assertIdentical('quickeditFieldFormSaved', $ajax_commands[0]['command'], 'The first AJAX command is a quickeditFieldFormSaved command.');
|
||||
$this->assertStringContainsString('Fine thanks.', $ajax_commands[0]['data'], 'Form value saved and printed back.');
|
||||
$this->assertStringContainsString('<div class="quickedit-test-wrapper">', $ajax_commands[0]['data'], 'Custom render pipeline used to render the value.');
|
||||
$this->assertIdentical(array_keys($ajax_commands[0]['other_view_modes']), ['full'], 'Field was also rendered in the "full" view mode.');
|
||||
$this->assertStringContainsString('Fine thanks.', $ajax_commands[0]['other_view_modes']['full'], '"full" version of field contains the form value.');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\quickedit\Functional;
|
||||
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use GuzzleHttp\RequestOptions;
|
||||
|
||||
/**
|
||||
* Tests accessing the Quick Edit endpoints.
|
||||
*
|
||||
* @group quickedit
|
||||
*/
|
||||
class QuickEditEndPointAccessTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'quickedit',
|
||||
'node',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->drupalCreateContentType([
|
||||
'type' => 'article',
|
||||
'name' => 'Article',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that Quick Edit endpoints are protected from anonymous requests.
|
||||
*/
|
||||
public function testEndPointAccess() {
|
||||
// Quick Edit's JavaScript would never hit these endpoints, but we need to
|
||||
// make sure that malicious users aren't able to use any of the other
|
||||
// endpoints either.
|
||||
$url = $this->buildUrl('/quickedit/attachments');
|
||||
$post = ['editors[0]' => 'form'];
|
||||
$this->assertAccessIsBlocked($url, $post);
|
||||
|
||||
$node = $this->createNode(['type' => 'article']);
|
||||
$url = $this->buildUrl('quickedit/form/node/' . $node->id() . '/body/en/full');
|
||||
$post = ['nocssjs' => 'true'];
|
||||
$this->assertAccessIsBlocked($url, $post);
|
||||
|
||||
$edit = [];
|
||||
$edit['form_id'] = 'quickedit_field_form';
|
||||
$edit['form_token'] = 'xIOzMjuc-PULKsRn_KxFn7xzNk5Bx7XKXLfQfw1qOnA';
|
||||
$edit['form_build_id'] = 'form-kVmovBpyX-SJfTT5kY0pjTV35TV-znor--a64dEnMR8';
|
||||
$edit['body[0][summary]'] = '';
|
||||
$edit['body[0][value]'] = '<p>Malicious content.</p>';
|
||||
$edit['body[0][format]'] = 'filtered_html';
|
||||
$edit['op'] = t('Save');
|
||||
$this->assertAccessIsBlocked($url, $edit);
|
||||
|
||||
$post = ['nocssjs' => 'true'];
|
||||
$url = $this->buildUrl('quickedit/entity/node/' . $node->id());
|
||||
$this->assertAccessIsBlocked($url, $post);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that access to the passed URL is blocked.
|
||||
*
|
||||
* @param string $url
|
||||
* The URL to check.
|
||||
* @param array $body
|
||||
* The payload to send with the request.
|
||||
*/
|
||||
protected function assertAccessIsBlocked($url, array $body) {
|
||||
$client = $this->getHttpClient();
|
||||
$message = ['message' => "The 'access in-place editing' permission is required."];
|
||||
|
||||
$response = $client->post($url, [
|
||||
RequestOptions::BODY => http_build_query($body),
|
||||
RequestOptions::QUERY => [MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_ajax'],
|
||||
RequestOptions::COOKIES => $this->getSessionCookies(),
|
||||
RequestOptions::HEADERS => [
|
||||
'Accept' => 'application/json',
|
||||
'Content-Type' => 'application/x-www-form-urlencoded',
|
||||
],
|
||||
RequestOptions::HTTP_ERRORS => FALSE,
|
||||
]);
|
||||
|
||||
$this->assertEquals(403, $response->getStatusCode());
|
||||
|
||||
$response_message = Json::decode($response->getBody());
|
||||
$this->assertSame($message, $response_message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\quickedit\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests Quick Edit can be installed with Minimal.
|
||||
*
|
||||
* @group quickedit
|
||||
*/
|
||||
class QuickEditMinimalTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $profile = 'minimal';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'quickedit',
|
||||
'quickedit_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests that Quick Edit works with no admin theme.
|
||||
*
|
||||
* @see \quickedit_library_info_alter()
|
||||
*/
|
||||
public function testSuccessfulInstall() {
|
||||
$editor_user = $this->drupalCreateUser([
|
||||
'access in-place editing',
|
||||
]);
|
||||
$this->drupalLogin($editor_user);
|
||||
$this->assertSame('', $this->config('system.theme')->get('admin'), 'There is no admin theme set on the site.');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\quickedit\FunctionalJavascript;
|
||||
|
||||
use Drupal\editor\Entity\Editor;
|
||||
use Drupal\filter\Entity\FilterFormat;
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\Tests\contextual\FunctionalJavascript\ContextualLinkClickTrait;
|
||||
|
||||
/**
|
||||
* Tests quickedit.
|
||||
*
|
||||
* @group quickedit
|
||||
*/
|
||||
class FieldTest extends WebDriverTestBase {
|
||||
|
||||
use ContextualLinkClickTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = [
|
||||
'node',
|
||||
'ckeditor',
|
||||
'contextual',
|
||||
'quickedit',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Create a text format and associate CKEditor.
|
||||
$filtered_html_format = FilterFormat::create([
|
||||
'format' => 'filtered_html',
|
||||
'name' => 'Filtered HTML',
|
||||
'weight' => 0,
|
||||
]);
|
||||
$filtered_html_format->save();
|
||||
|
||||
Editor::create([
|
||||
'format' => 'filtered_html',
|
||||
'editor' => 'ckeditor',
|
||||
])->save();
|
||||
|
||||
// Create note type with body field.
|
||||
$node_type = NodeType::create(['type' => 'page', 'name' => 'Page']);
|
||||
$node_type->save();
|
||||
node_add_body_field($node_type);
|
||||
|
||||
$account = $this->drupalCreateUser([
|
||||
'access content',
|
||||
'administer nodes',
|
||||
'edit any page content',
|
||||
'use text format filtered_html',
|
||||
'access contextual links',
|
||||
'access in-place editing',
|
||||
]);
|
||||
$this->drupalLogin($account);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that quickeditor works correctly for field with CKEditor.
|
||||
*/
|
||||
public function testFieldWithCkeditor() {
|
||||
$body_value = '<p>Sapere aude</p>';
|
||||
$node = Node::create([
|
||||
'type' => 'page',
|
||||
'title' => 'Page node',
|
||||
'body' => [['value' => $body_value, 'format' => 'filtered_html']],
|
||||
]);
|
||||
$node->save();
|
||||
|
||||
$page = $this->getSession()->getPage();
|
||||
$assert = $this->assertSession();
|
||||
|
||||
$this->drupalGet('node/' . $node->id());
|
||||
|
||||
// Wait "Quick edit" button for node.
|
||||
$this->assertSession()->waitForElement('css', '[data-quickedit-entity-id="node/' . $node->id() . '"] .contextual .quickedit');
|
||||
// Click by "Quick edit".
|
||||
$this->clickContextualLink('[data-quickedit-entity-id="node/' . $node->id() . '"]', 'Quick edit');
|
||||
// Switch to body field.
|
||||
$page->find('css', '[data-quickedit-field-id="node/' . $node->id() . '/body/en/full"]')->click();
|
||||
// Wait and click by "Blockquote" button from editor for body field.
|
||||
$this->assertSession()->waitForElementVisible('css', '.cke_button.cke_button__blockquote')->click();
|
||||
// Wait and click by "Save" button after body field was changed.
|
||||
$this->assertSession()->waitForElementVisible('css', '.quickedit-toolgroup.ops [type="submit"][aria-hidden="false"]')->click();
|
||||
// Wait until the save occurs and the editor UI disappears.
|
||||
$this->assertSession()->assertNoElementAfterWait('css', '.cke_button.cke_button__blockquote');
|
||||
// Ensure that the changes take effect.
|
||||
$assert->responseMatches("|<blockquote>\s*$body_value\s*</blockquote>|");
|
||||
}
|
||||
|
||||
}
|
||||
+209
@@ -0,0 +1,209 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\quickedit\FunctionalJavascript;
|
||||
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
use Drupal\taxonomy\Entity\Vocabulary;
|
||||
use Drupal\taxonomy\Entity\Term;
|
||||
use Drupal\Tests\contextual\FunctionalJavascript\ContextualLinkClickTrait;
|
||||
use Drupal\Tests\field\Traits\EntityReferenceTestTrait;
|
||||
|
||||
/**
|
||||
* Tests in-place editing of autocomplete tags.
|
||||
*
|
||||
* @group quickedit
|
||||
*/
|
||||
class QuickEditAutocompleteTermTest extends WebDriverTestBase {
|
||||
|
||||
use EntityReferenceTestTrait;
|
||||
use ContextualLinkClickTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = [
|
||||
'node',
|
||||
'taxonomy',
|
||||
'quickedit',
|
||||
'contextual',
|
||||
'ckeditor',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Stores the node used for the tests.
|
||||
*
|
||||
* @var \Drupal\node\NodeInterface
|
||||
*/
|
||||
protected $node;
|
||||
|
||||
/**
|
||||
* Stores the vocabulary used in the tests.
|
||||
*
|
||||
* @var \Drupal\taxonomy\VocabularyInterface
|
||||
*/
|
||||
protected $vocabulary;
|
||||
|
||||
/**
|
||||
* Stores the first term used in the tests.
|
||||
*
|
||||
* @var \Drupal\taxonomy\TermInterface
|
||||
*/
|
||||
protected $term1;
|
||||
|
||||
/**
|
||||
* Stores the second term used in the tests.
|
||||
*
|
||||
* @var \Drupal\taxonomy\TermInterface
|
||||
*/
|
||||
protected $term2;
|
||||
|
||||
/**
|
||||
* Stores the field name for the autocomplete field.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldName;
|
||||
|
||||
/**
|
||||
* An user with permissions to access in-place editor.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $editorUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalCreateContentType([
|
||||
'type' => 'article',
|
||||
]);
|
||||
$this->vocabulary = Vocabulary::create([
|
||||
'name' => 'quickedit testing tags',
|
||||
'vid' => 'quickedit_testing_tags',
|
||||
]);
|
||||
$this->vocabulary->save();
|
||||
$this->fieldName = 'field_' . $this->vocabulary->id();
|
||||
|
||||
$handler_settings = [
|
||||
'target_bundles' => [
|
||||
$this->vocabulary->id() => $this->vocabulary->id(),
|
||||
],
|
||||
'auto_create' => TRUE,
|
||||
];
|
||||
$this->createEntityReferenceField('node', 'article', $this->fieldName, 'Tags', 'taxonomy_term', 'default', $handler_settings, FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
|
||||
|
||||
\Drupal::service('entity_display.repository')->getFormDisplay('node', 'article')
|
||||
->setComponent($this->fieldName, [
|
||||
'type' => 'entity_reference_autocomplete_tags',
|
||||
'weight' => -4,
|
||||
])
|
||||
->save();
|
||||
|
||||
\Drupal::service('entity_display.repository')->getViewDisplay('node', 'article')
|
||||
->setComponent($this->fieldName, [
|
||||
'type' => 'entity_reference_label',
|
||||
'weight' => 10,
|
||||
])
|
||||
->save();
|
||||
\Drupal::service('entity_display.repository')->getViewDisplay('node', 'article', 'teaser')
|
||||
->setComponent($this->fieldName, [
|
||||
'type' => 'entity_reference_label',
|
||||
'weight' => 10,
|
||||
])
|
||||
->save();
|
||||
|
||||
$this->term1 = $this->createTerm();
|
||||
$this->term2 = $this->createTerm();
|
||||
|
||||
$node = [];
|
||||
$node['type'] = 'article';
|
||||
$node[$this->fieldName][]['target_id'] = $this->term1->id();
|
||||
$node[$this->fieldName][]['target_id'] = $this->term2->id();
|
||||
$this->node = $this->drupalCreateNode($node);
|
||||
|
||||
$this->editorUser = $this->drupalCreateUser([
|
||||
'access content',
|
||||
'create article content',
|
||||
'edit any article content',
|
||||
'administer nodes',
|
||||
'access contextual links',
|
||||
'access in-place editing',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests Quick Edit autocomplete term behavior.
|
||||
*/
|
||||
public function testAutocompleteQuickEdit() {
|
||||
$page = $this->getSession()->getPage();
|
||||
$assert = $this->assertSession();
|
||||
|
||||
$this->drupalLogin($this->editorUser);
|
||||
$this->drupalGet('node/' . $this->node->id());
|
||||
|
||||
// Wait "Quick edit" button for node.
|
||||
$assert->waitForElement('css', '[data-quickedit-entity-id="node/' . $this->node->id() . '"] .contextual .quickedit');
|
||||
// Click by "Quick edit".
|
||||
$this->clickContextualLink('[data-quickedit-entity-id="node/' . $this->node->id() . '"]', 'Quick edit');
|
||||
// Switch to body field.
|
||||
$page->find('css', '[data-quickedit-field-id="node/' . $this->node->id() . '/' . $this->fieldName . '/' . $this->node->language()->getId() . '/full"]')->click();
|
||||
|
||||
// Open Quick Edit.
|
||||
$quickedit_field_locator = '[name="field_quickedit_testing_tags[target_id]"]';
|
||||
$tag_field = $assert->waitForElementVisible('css', $quickedit_field_locator);
|
||||
$tag_field->focus();
|
||||
$tags = $tag_field->getValue();
|
||||
|
||||
// Check existing terms.
|
||||
$this->assertStringContainsString($this->term1->label(), $tags);
|
||||
$this->assertStringContainsString($this->term2->label(), $tags);
|
||||
|
||||
// Add new term.
|
||||
$new_tag = $this->randomMachineName();
|
||||
$tags .= ', ' . $new_tag;
|
||||
$assert->waitForElementVisible('css', $quickedit_field_locator)->setValue($tags);
|
||||
$assert->waitOnAutocomplete();
|
||||
// Wait and click by "Save" button after body field was changed.
|
||||
$assert->waitForElementVisible('css', '.quickedit-toolgroup.ops [type="submit"][aria-hidden="false"]')->click();
|
||||
$assert->waitOnAutocomplete();
|
||||
|
||||
// Reload the page and check new term.
|
||||
$this->drupalGet('node/' . $this->node->id());
|
||||
$link = $assert->waitForLink($new_tag);
|
||||
$this->assertNotEmpty($link);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new term with random name and description in $this->vocabulary.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface|\Drupal\taxonomy\Entity\Term
|
||||
* The created taxonomy term.
|
||||
*
|
||||
* @throws \Drupal\Core\Entity\EntityStorageException
|
||||
*/
|
||||
protected function createTerm() {
|
||||
$filter_formats = filter_formats();
|
||||
$format = array_pop($filter_formats);
|
||||
$term = Term::create([
|
||||
'name' => $this->randomMachineName(),
|
||||
'description' => $this->randomMachineName(),
|
||||
// Use the first available text format.
|
||||
'format' => $format->id(),
|
||||
'vid' => $this->vocabulary->id(),
|
||||
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
|
||||
]);
|
||||
$term->save();
|
||||
return $term;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\quickedit\FunctionalJavascript;
|
||||
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\Tests\file\Functional\FileFieldCreationTrait;
|
||||
use Drupal\Tests\TestFileCreationTrait;
|
||||
|
||||
/**
|
||||
* @group quickedit
|
||||
*/
|
||||
class QuickEditFileTest extends QuickEditJavascriptTestBase {
|
||||
|
||||
use FileFieldCreationTrait;
|
||||
use TestFileCreationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = [
|
||||
'node',
|
||||
'file',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Create the Article node type.
|
||||
$this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
|
||||
|
||||
// Add file field to Article node type.
|
||||
$this->createFileField('field_file', 'node', 'article', ['file_extensions' => 'txt']);
|
||||
|
||||
// Log in as a content author who can use Quick Edit and edit Articles.
|
||||
$user = $this->drupalCreateUser([
|
||||
'access contextual links',
|
||||
'access toolbar',
|
||||
'access in-place editing',
|
||||
'access content',
|
||||
'create article content',
|
||||
'edit any article content',
|
||||
]);
|
||||
$this->drupalLogin($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if a file can be in-place removed with Quick Edit.
|
||||
*/
|
||||
public function testRemove() {
|
||||
$assert_session = $this->assertSession();
|
||||
|
||||
// Create test file.
|
||||
$this->generateFile('test', 64, 10, 'text');
|
||||
$file = File::create([
|
||||
'uri' => 'public://test.txt',
|
||||
'filename' => 'test.txt',
|
||||
]);
|
||||
$file->setPermanent();
|
||||
$file->save();
|
||||
|
||||
// Create test node.
|
||||
$node = $this->drupalCreateNode([
|
||||
'type' => 'article',
|
||||
'title' => t('My Test Node'),
|
||||
'field_file' => [
|
||||
'target_id' => $file->id(),
|
||||
],
|
||||
]);
|
||||
|
||||
$this->drupalGet($node->toUrl()->toString());
|
||||
|
||||
// Start Quick Edit.
|
||||
$this->awaitQuickEditForEntity('node', 1);
|
||||
$this->startQuickEditViaToolbar('node', 1, 0);
|
||||
|
||||
// Click the file field.
|
||||
$assert_session->waitForElementVisible('css', '[data-quickedit-field-id="node/1/field_file/en/full"]');
|
||||
$this->click('[data-quickedit-field-id="node/1/field_file/en/full"]');
|
||||
$assert_session->waitForElement('css', '.quickedit-toolbar-field div[id*="file"]');
|
||||
|
||||
// Remove the file.
|
||||
$remove = $assert_session->waitForButton('Remove');
|
||||
$remove->click();
|
||||
// Wait for remove.
|
||||
$assert_session->waitForElement('css', 'input[name="files[field_file_0]"]');
|
||||
$this->saveQuickEdit();
|
||||
// Wait for save.
|
||||
$this->assertJsCondition("Drupal.quickedit.collections.entities.get('node/1[0]').get('state') === 'closed'");
|
||||
|
||||
// Assert file is removed from node.
|
||||
$assert_session->pageTextNotContains('test.txt');
|
||||
$node = Node::load($node->id());
|
||||
$this->assertEmpty($node->get('field_file')->getValue());
|
||||
}
|
||||
|
||||
}
|
||||
+348
@@ -0,0 +1,348 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\quickedit\FunctionalJavascript;
|
||||
|
||||
use Drupal\block_content\Entity\BlockContent;
|
||||
use Drupal\block_content\Entity\BlockContentType;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\editor\Entity\Editor;
|
||||
use Drupal\filter\Entity\FilterFormat;
|
||||
use Drupal\taxonomy\Entity\Term;
|
||||
use Drupal\taxonomy\Entity\Vocabulary;
|
||||
use Drupal\Tests\field\Traits\EntityReferenceTestTrait;
|
||||
|
||||
/**
|
||||
* @group quickedit
|
||||
*/
|
||||
class QuickEditIntegrationTest extends QuickEditJavascriptTestBase {
|
||||
|
||||
use EntityReferenceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = [
|
||||
'node',
|
||||
'editor',
|
||||
'ckeditor',
|
||||
'taxonomy',
|
||||
'block',
|
||||
'block_content',
|
||||
'hold_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'classy';
|
||||
|
||||
/**
|
||||
* A user with permissions to edit Articles and use Quick Edit.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $contentAuthorUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
// Create text format, associate CKEditor.
|
||||
FilterFormat::create([
|
||||
'format' => 'some_format',
|
||||
'name' => 'Some format',
|
||||
'weight' => 0,
|
||||
'filters' => [
|
||||
'filter_html' => [
|
||||
'status' => 1,
|
||||
'settings' => [
|
||||
'allowed_html' => '<h2 id> <h3> <h4> <h5> <h6> <p> <br> <strong> <a href hreflang>',
|
||||
],
|
||||
],
|
||||
],
|
||||
])->save();
|
||||
Editor::create([
|
||||
'format' => 'some_format',
|
||||
'editor' => 'ckeditor',
|
||||
])->save();
|
||||
|
||||
// Create the Article node type.
|
||||
$this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
|
||||
|
||||
// Add "tags" vocabulary + field to the Article node type.
|
||||
$vocabulary = Vocabulary::create([
|
||||
'name' => 'Tags',
|
||||
'vid' => 'tags',
|
||||
]);
|
||||
$vocabulary->save();
|
||||
$field_name = 'field_' . $vocabulary->id();
|
||||
$handler_settings = [
|
||||
'target_bundles' => [
|
||||
$vocabulary->id() => $vocabulary->id(),
|
||||
],
|
||||
'auto_create' => TRUE,
|
||||
];
|
||||
$this->createEntityReferenceField('node', 'article', $field_name, 'Tags', 'taxonomy_term', 'default', $handler_settings, FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
|
||||
|
||||
// Add formatter & widget for "tags" field.
|
||||
\Drupal::entityTypeManager()
|
||||
->getStorage('entity_form_display')
|
||||
->load('node.article.default')
|
||||
->setComponent($field_name, ['type' => 'entity_reference_autocomplete_tags'])
|
||||
->save();
|
||||
\Drupal::entityTypeManager()
|
||||
->getStorage('entity_view_display')
|
||||
->load('node.article.default')
|
||||
->setComponent($field_name, ['type' => 'entity_reference_label'])
|
||||
->save();
|
||||
|
||||
$this->drupalPlaceBlock('page_title_block');
|
||||
$this->drupalPlaceBlock('system_main_block');
|
||||
|
||||
// Log in as a content author who can use Quick Edit and edit Articles.
|
||||
$this->contentAuthorUser = $this->drupalCreateUser([
|
||||
'access contextual links',
|
||||
'access toolbar',
|
||||
'access in-place editing',
|
||||
'access content',
|
||||
'create article content',
|
||||
'edit any article content',
|
||||
'use text format some_format',
|
||||
'edit terms in tags',
|
||||
'administer blocks',
|
||||
]);
|
||||
$this->drupalLogin($this->contentAuthorUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if an article node can be in-place edited with Quick Edit.
|
||||
*/
|
||||
public function testArticleNode() {
|
||||
$term = Term::create([
|
||||
'name' => 'foo',
|
||||
'vid' => 'tags',
|
||||
]);
|
||||
$term->save();
|
||||
|
||||
$node = $this->drupalCreateNode([
|
||||
'type' => 'article',
|
||||
'title' => t('My Test Node'),
|
||||
'body' => [
|
||||
'value' => '<p>Hello world!</p><p>I do not know what to say…</p><p>I wish I were eloquent.</p>',
|
||||
'format' => 'some_format',
|
||||
],
|
||||
'field_tags' => [
|
||||
['target_id' => $term->id()],
|
||||
],
|
||||
]);
|
||||
|
||||
$this->drupalGet('node/' . $node->id());
|
||||
|
||||
// Initial state.
|
||||
$this->awaitQuickEditForEntity('node', 1);
|
||||
$this->assertEntityInstanceStates([
|
||||
'node/1[0]' => 'closed',
|
||||
]);
|
||||
$this->assertEntityInstanceFieldStates('node', 1, 0, [
|
||||
'node/1/title/en/full' => 'inactive',
|
||||
'node/1/uid/en/full' => 'inactive',
|
||||
'node/1/created/en/full' => 'inactive',
|
||||
'node/1/body/en/full' => 'inactive',
|
||||
'node/1/field_tags/en/full' => 'inactive',
|
||||
]);
|
||||
|
||||
// Start in-place editing of the article node.
|
||||
$this->startQuickEditViaToolbar('node', 1, 0);
|
||||
$this->assertEntityInstanceStates([
|
||||
'node/1[0]' => 'opened',
|
||||
]);
|
||||
$this->assertQuickEditEntityToolbar((string) $node->label(), NULL);
|
||||
$this->assertEntityInstanceFieldStates('node', 1, 0, [
|
||||
'node/1/title/en/full' => 'candidate',
|
||||
'node/1/uid/en/full' => 'candidate',
|
||||
'node/1/created/en/full' => 'candidate',
|
||||
'node/1/body/en/full' => 'candidate',
|
||||
'node/1/field_tags/en/full' => 'candidate',
|
||||
]);
|
||||
|
||||
$assert_session = $this->assertSession();
|
||||
|
||||
// Click the title field.
|
||||
$this->click('[data-quickedit-field-id="node/1/title/en/full"].quickedit-candidate');
|
||||
$assert_session->waitForElement('css', '.quickedit-toolbar-field div[id*="title"]');
|
||||
$this->assertQuickEditEntityToolbar((string) $node->label(), 'Title');
|
||||
$this->assertEntityInstanceFieldStates('node', 1, 0, [
|
||||
'node/1/title/en/full' => 'active',
|
||||
'node/1/uid/en/full' => 'candidate',
|
||||
'node/1/created/en/full' => 'candidate',
|
||||
'node/1/body/en/full' => 'candidate',
|
||||
'node/1/field_tags/en/full' => 'candidate',
|
||||
]);
|
||||
$this->assertEntityInstanceFieldMarkup('node', 1, 0, [
|
||||
'node/1/title/en/full' => '[contenteditable="true"]',
|
||||
]);
|
||||
|
||||
// Append something to the title.
|
||||
$this->typeInPlainTextEditor('[data-quickedit-field-id="node/1/title/en/full"].quickedit-candidate', ' Llamas are awesome!');
|
||||
$this->awaitEntityInstanceFieldState('node', 1, 0, 'title', 'en', 'changed');
|
||||
$this->assertEntityInstanceFieldStates('node', 1, 0, [
|
||||
'node/1/title/en/full' => 'changed',
|
||||
'node/1/uid/en/full' => 'candidate',
|
||||
'node/1/created/en/full' => 'candidate',
|
||||
'node/1/body/en/full' => 'candidate',
|
||||
'node/1/field_tags/en/full' => 'candidate',
|
||||
]);
|
||||
|
||||
// Click the body field.
|
||||
hold_test_response(TRUE);
|
||||
$this->click('[data-quickedit-entity-id="node/1"] .field--name-body');
|
||||
$assert_session->waitForElement('css', '.quickedit-toolbar-field div[id*="body"]');
|
||||
$this->assertQuickEditEntityToolbar((string) $node->label(), 'Body');
|
||||
$this->assertEntityInstanceFieldStates('node', 1, 0, [
|
||||
'node/1/title/en/full' => 'saving',
|
||||
'node/1/uid/en/full' => 'candidate',
|
||||
'node/1/created/en/full' => 'candidate',
|
||||
'node/1/body/en/full' => 'active',
|
||||
'node/1/field_tags/en/full' => 'candidate',
|
||||
]);
|
||||
hold_test_response(FALSE);
|
||||
|
||||
// Wait for CKEditor to load, then verify it has.
|
||||
$this->assertJsCondition('CKEDITOR.status === "loaded"');
|
||||
$this->assertEntityInstanceFieldMarkup('node', 1, 0, [
|
||||
'node/1/body/en/full' => '.cke_editable_inline',
|
||||
'node/1/field_tags/en/full' => ':not(.quickedit-editor-is-popup)',
|
||||
]);
|
||||
$this->assertSession()->elementExists('css', '#quickedit-entity-toolbar .quickedit-toolgroup.wysiwyg-main > .cke_chrome .cke_top[role="presentation"] .cke_toolbar[role="toolbar"] .cke_toolgroup[role="presentation"] > .cke_button[title~="Bold"][role="button"]');
|
||||
|
||||
// Wait for the validating & saving of the title to complete.
|
||||
$this->awaitEntityInstanceFieldState('node', 1, 0, 'title', 'en', 'candidate');
|
||||
|
||||
// Click the tags field.
|
||||
hold_test_response(TRUE);
|
||||
$this->click('[data-quickedit-field-id="node/1/field_tags/en/full"]');
|
||||
$assert_session->waitForElement('css', '.quickedit-toolbar-field div[id*="tags"]');
|
||||
$this->assertQuickEditEntityToolbar((string) $node->label(), 'Tags');
|
||||
$this->assertEntityInstanceFieldStates('node', 1, 0, [
|
||||
'node/1/uid/en/full' => 'candidate',
|
||||
'node/1/created/en/full' => 'candidate',
|
||||
'node/1/body/en/full' => 'candidate',
|
||||
'node/1/field_tags/en/full' => 'activating',
|
||||
'node/1/title/en/full' => 'candidate',
|
||||
]);
|
||||
$this->assertEntityInstanceFieldMarkup('node', 1, 0, [
|
||||
'node/1/title/en/full' => '.quickedit-changed',
|
||||
'node/1/field_tags/en/full' => '.quickedit-editor-is-popup',
|
||||
]);
|
||||
// Assert the "Loading…" popup appears.
|
||||
$this->assertSession()->elementExists('css', '.quickedit-form-container > .quickedit-form[role="dialog"] > .placeholder');
|
||||
hold_test_response(FALSE);
|
||||
// Wait for the form to load.
|
||||
$this->assertJsCondition('document.querySelector(\'.quickedit-form-container > .quickedit-form[role="dialog"] > .placeholder\') === null');
|
||||
$this->assertEntityInstanceFieldStates('node', 1, 0, [
|
||||
'node/1/uid/en/full' => 'candidate',
|
||||
'node/1/created/en/full' => 'candidate',
|
||||
'node/1/body/en/full' => 'candidate',
|
||||
'node/1/field_tags/en/full' => 'active',
|
||||
'node/1/title/en/full' => 'candidate',
|
||||
]);
|
||||
|
||||
// Enter an additional tag.
|
||||
$this->typeInFormEditorTextInputField('field_tags[target_id]', 'foo, bar');
|
||||
$this->awaitEntityInstanceFieldState('node', 1, 0, 'field_tags', 'en', 'changed');
|
||||
$this->assertEntityInstanceFieldStates('node', 1, 0, [
|
||||
'node/1/uid/en/full' => 'candidate',
|
||||
'node/1/created/en/full' => 'candidate',
|
||||
'node/1/body/en/full' => 'candidate',
|
||||
'node/1/field_tags/en/full' => 'changed',
|
||||
'node/1/title/en/full' => 'candidate',
|
||||
]);
|
||||
|
||||
// Click 'Save'.
|
||||
hold_test_response(TRUE);
|
||||
$this->saveQuickEdit();
|
||||
$this->assertEntityInstanceStates([
|
||||
'node/1[0]' => 'committing',
|
||||
]);
|
||||
$this->assertEntityInstanceFieldStates('node', 1, 0, [
|
||||
'node/1/uid/en/full' => 'candidate',
|
||||
'node/1/created/en/full' => 'candidate',
|
||||
'node/1/body/en/full' => 'candidate',
|
||||
'node/1/field_tags/en/full' => 'saving',
|
||||
'node/1/title/en/full' => 'candidate',
|
||||
]);
|
||||
hold_test_response(FALSE);
|
||||
$this->assertEntityInstanceFieldMarkup('node', 1, 0, [
|
||||
'node/1/title/en/full' => '.quickedit-changed',
|
||||
'node/1/field_tags/en/full' => '.quickedit-changed',
|
||||
]);
|
||||
|
||||
// Wait for the saving of the tags field to complete.
|
||||
$this->assertJsCondition("Drupal.quickedit.collections.entities.get('node/1[0]').get('state') === 'closed'");
|
||||
$this->assertEntityInstanceStates([
|
||||
'node/1[0]' => 'closed',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if a custom can be in-place edited with Quick Edit.
|
||||
*/
|
||||
public function testCustomBlock() {
|
||||
$block_content_type = BlockContentType::create([
|
||||
'id' => 'basic',
|
||||
'label' => 'basic',
|
||||
'revision' => FALSE,
|
||||
]);
|
||||
$block_content_type->save();
|
||||
block_content_add_body_field($block_content_type->id());
|
||||
|
||||
$block_content = BlockContent::create([
|
||||
'info' => 'Llama',
|
||||
'type' => 'basic',
|
||||
'body' => [
|
||||
'value' => 'The name "llama" was adopted by European settlers from native Peruvians.',
|
||||
'format' => 'some_format',
|
||||
],
|
||||
]);
|
||||
$block_content->save();
|
||||
$this->drupalPlaceBlock('block_content:' . $block_content->uuid(), [
|
||||
'label' => 'My custom block!',
|
||||
]);
|
||||
|
||||
$this->drupalGet('');
|
||||
|
||||
// Initial state.
|
||||
$this->awaitQuickEditForEntity('block_content', 1);
|
||||
$this->assertEntityInstanceStates([
|
||||
'block_content/1[0]' => 'closed',
|
||||
]);
|
||||
|
||||
// Start in-place editing of the article node.
|
||||
$this->startQuickEditViaToolbar('block_content', 1, 0);
|
||||
$this->assertEntityInstanceStates([
|
||||
'block_content/1[0]' => 'opened',
|
||||
]);
|
||||
$this->assertQuickEditEntityToolbar((string) $block_content->label(), 'Body');
|
||||
$this->assertEntityInstanceFieldStates('block_content', 1, 0, [
|
||||
'block_content/1/body/en/full' => 'highlighted',
|
||||
]);
|
||||
|
||||
// Click the body field.
|
||||
$this->click('[data-quickedit-entity-id="block_content/1"] .field--name-body');
|
||||
$assert_session = $this->assertSession();
|
||||
$assert_session->waitForElement('css', '.quickedit-toolbar-field div[id*="body"]');
|
||||
$this->assertQuickEditEntityToolbar((string) $block_content->label(), 'Body');
|
||||
$this->assertEntityInstanceFieldStates('block_content', 1, 0, [
|
||||
'block_content/1/body/en/full' => 'active',
|
||||
]);
|
||||
|
||||
// Wait for CKEditor to load, then verify it has.
|
||||
$this->assertJsCondition('CKEDITOR.status === "loaded"');
|
||||
$this->assertEntityInstanceFieldMarkup('block_content', 1, 0, [
|
||||
'block_content/1/body/en/full' => '.cke_editable_inline',
|
||||
]);
|
||||
$this->assertSession()->elementExists('css', '#quickedit-entity-toolbar .quickedit-toolgroup.wysiwyg-main > .cke_chrome .cke_top[role="presentation"] .cke_toolbar[role="toolbar"] .cke_toolgroup[role="presentation"] > .cke_button[title~="Bold"][role="button"]');
|
||||
}
|
||||
|
||||
}
|
||||
+318
@@ -0,0 +1,318 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\quickedit\FunctionalJavascript;
|
||||
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
use WebDriver\Key;
|
||||
|
||||
/**
|
||||
* Base class for testing the QuickEdit.
|
||||
*/
|
||||
class QuickEditJavascriptTestBase extends WebDriverTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['contextual', 'quickedit', 'toolbar'];
|
||||
|
||||
/**
|
||||
* A user with permissions to edit Articles and use Quick Edit.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $contentAuthorUser;
|
||||
|
||||
protected static $expectedFieldStateAttributes = [
|
||||
'inactive' => '.quickedit-field:not(.quickedit-editable):not(.quickedit-candidate):not(.quickedit-highlighted):not(.quickedit-editing):not(.quickedit-changed)',
|
||||
// A field in 'candidate' state may still have the .quickedit-changed class
|
||||
// because when its changes were saved to tempstore, it'll still be changed.
|
||||
// It's just not currently being edited, so that's why it is not in the
|
||||
// 'changed' state.
|
||||
'candidate' => '.quickedit-field.quickedit-editable.quickedit-candidate:not(.quickedit-highlighted):not(.quickedit-editing)',
|
||||
'highlighted' => '.quickedit-field.quickedit-editable.quickedit-candidate.quickedit-highlighted:not(.quickedit-editing)',
|
||||
'activating' => '.quickedit-field.quickedit-editable.quickedit-candidate.quickedit-highlighted.quickedit-editing:not(.quickedit-changed)',
|
||||
'active' => '.quickedit-field.quickedit-editable.quickedit-candidate.quickedit-highlighted.quickedit-editing:not(.quickedit-changed)',
|
||||
'changed' => '.quickedit-field.quickedit-editable.quickedit-candidate.quickedit-highlighted.quickedit-editing.quickedit-changed',
|
||||
'saving' => '.quickedit-field.quickedit-editable.quickedit-candidate.quickedit-highlighted.quickedit-editing.quickedit-changed',
|
||||
];
|
||||
|
||||
/**
|
||||
* Starts in-place editing of the given entity instance.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The entity type ID.
|
||||
* @param int $entity_id
|
||||
* The entity ID.
|
||||
* @param int $entity_instance_id
|
||||
* The entity instance ID. (Instance on the page.)
|
||||
*/
|
||||
protected function startQuickEditViaToolbar($entity_type_id, $entity_id, $entity_instance_id) {
|
||||
$page = $this->getSession()->getPage();
|
||||
|
||||
$toolbar_edit_button_selector = '#toolbar-bar .contextual-toolbar-tab button';
|
||||
$entity_instance_selector = '[data-quickedit-entity-id="' . $entity_type_id . '/' . $entity_id . '"][data-quickedit-entity-instance-id="' . $entity_instance_id . '"]';
|
||||
$contextual_links_trigger_selector = '[data-contextual-id] > .trigger';
|
||||
|
||||
// Assert the original page state does not have the toolbar's "Edit" button
|
||||
// pressed/activated, and hence none of the contextual link triggers should
|
||||
// be visible.
|
||||
$toolbar_edit_button = $page->find('css', $toolbar_edit_button_selector);
|
||||
$this->assertSame('false', $toolbar_edit_button->getAttribute('aria-pressed'), 'The "Edit" button in the toolbar is not yet pressed.');
|
||||
$this->assertFalse($toolbar_edit_button->hasClass('is-active'), 'The "Edit" button in the toolbar is not yet marked as active.');
|
||||
foreach ($page->findAll('css', $contextual_links_trigger_selector) as $dom_node) {
|
||||
/** @var \Behat\Mink\Element\NodeElement $dom_node */
|
||||
$this->assertTrue($dom_node->hasClass('visually-hidden'), 'The contextual links trigger "' . $dom_node->getParent()->getAttribute('data-contextual-id') . '" is hidden.');
|
||||
}
|
||||
$this->assertTrue(TRUE, 'All contextual links triggers are hidden.');
|
||||
|
||||
// Click the "Edit" button in the toolbar.
|
||||
$this->click($toolbar_edit_button_selector);
|
||||
|
||||
// Assert the toolbar's "Edit" button is now pressed/activated, and hence
|
||||
// all of the contextual link triggers should be visible.
|
||||
$this->assertSame('true', $toolbar_edit_button->getAttribute('aria-pressed'), 'The "Edit" button in the toolbar is pressed.');
|
||||
$this->assertTrue($toolbar_edit_button->hasClass('is-active'), 'The "Edit" button in the toolbar is marked as active.');
|
||||
foreach ($page->findAll('css', $contextual_links_trigger_selector) as $dom_node) {
|
||||
/** @var \Behat\Mink\Element\NodeElement $dom_node */
|
||||
$this->assertFalse($dom_node->hasClass('visually-hidden'), 'The contextual links trigger "' . $dom_node->getParent()->getAttribute('data-contextual-id') . '" is visible.');
|
||||
}
|
||||
$this->assertTrue(TRUE, 'All contextual links triggers are visible.');
|
||||
|
||||
// @todo Press tab key to verify that tabbing is now contrained to only
|
||||
// contextual links triggers: https://www.drupal.org/node/2834776
|
||||
|
||||
// Assert that the contextual links associated with the entity's contextual
|
||||
// links trigger are not visible.
|
||||
/** @var \Behat\Mink\Element\NodeElement $entity_contextual_links_container */
|
||||
$entity_contextual_links_container = $page->find('css', $entity_instance_selector)
|
||||
->find('css', $contextual_links_trigger_selector)
|
||||
->getParent();
|
||||
$this->assertFalse($entity_contextual_links_container->hasClass('open'));
|
||||
$this->assertTrue($entity_contextual_links_container->find('css', 'ul.contextual-links')->hasAttribute('hidden'));
|
||||
|
||||
// Click the contextual link trigger for the entity we want to Quick Edit.
|
||||
$this->click($entity_instance_selector . ' ' . $contextual_links_trigger_selector);
|
||||
|
||||
$this->assertTrue($entity_contextual_links_container->hasClass('open'));
|
||||
$this->assertFalse($entity_contextual_links_container->find('css', 'ul.contextual-links')->hasAttribute('hidden'));
|
||||
|
||||
// Click the "Quick edit" contextual link.
|
||||
$this->click($entity_instance_selector . ' [data-contextual-id] ul.contextual-links li.quickedit a');
|
||||
|
||||
// Assert the Quick Edit internal state is correct.
|
||||
$js_condition = <<<JS
|
||||
Drupal.quickedit.collections.entities.where({isActive: true}).length === 1 && Drupal.quickedit.collections.entities.where({isActive: true})[0].get('entityID') === '$entity_type_id/$entity_id';
|
||||
JS;
|
||||
$this->assertJsCondition($js_condition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clicks the 'Save' button in the Quick Edit entity toolbar.
|
||||
*/
|
||||
protected function saveQuickEdit() {
|
||||
$quickedit_entity_toolbar = $this->getSession()->getPage()->findById('quickedit-entity-toolbar');
|
||||
$save_button = $quickedit_entity_toolbar->find('css', 'button.action-save');
|
||||
$save_button->press();
|
||||
$this->assertSame('Saving', $save_button->getText());
|
||||
}
|
||||
|
||||
/**
|
||||
* Awaits Quick Edit to be initiated for all instances of the given entity.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The entity type ID.
|
||||
* @param int $entity_id
|
||||
* The entity ID.
|
||||
*/
|
||||
protected function awaitQuickEditForEntity($entity_type_id, $entity_id) {
|
||||
$entity_selector = '[data-quickedit-entity-id="' . $entity_type_id . '/' . $entity_id . '"]';
|
||||
$condition = "document.querySelectorAll('" . $entity_selector . "').length === document.querySelectorAll('" . $entity_selector . " .quickedit').length";
|
||||
$this->assertJsCondition($condition, 10000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Awaits a particular field instance to reach a particular state.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The entity type ID.
|
||||
* @param int $entity_id
|
||||
* The entity ID.
|
||||
* @param int $entity_instance_id
|
||||
* The entity instance ID. (Instance on the page.)
|
||||
* @param string $field_name
|
||||
* The field name.
|
||||
* @param string $langcode
|
||||
* The language code.
|
||||
* @param string $awaited_state
|
||||
* One of the possible field states.
|
||||
*/
|
||||
protected function awaitEntityInstanceFieldState($entity_type_id, $entity_id, $entity_instance_id, $field_name, $langcode, $awaited_state) {
|
||||
$entity_page_id = $entity_type_id . '/' . $entity_id . '[' . $entity_instance_id . ']';
|
||||
$logical_field_id = $entity_type_id . '/' . $entity_id . '/' . $field_name . '/' . $langcode;
|
||||
$this->assertJsCondition("Drupal.quickedit.collections.entities.get('$entity_page_id').get('fields').findWhere({logicalFieldID: '$logical_field_id'}).get('state') === '$awaited_state';");
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the state of the Quick Edit entity toolbar.
|
||||
*
|
||||
* @param string $expected_entity_label
|
||||
* The expected label in the Quick Edit Entity Toolbar.
|
||||
*/
|
||||
protected function assertQuickEditEntityToolbar($expected_entity_label, $expected_field_label) {
|
||||
$quickedit_entity_toolbar = $this->getSession()->getPage()->findById('quickedit-entity-toolbar');
|
||||
// We cannot use ->getText() because it also returns the text of all child
|
||||
// nodes. We also cannot use XPath to select text node in Selenium. So we
|
||||
// use JS expression to select only the text node.
|
||||
$this->assertSame($expected_entity_label, $this->getSession()->evaluateScript("return window.jQuery('#quickedit-entity-toolbar .quickedit-toolbar-label').clone().children().remove().end().text();"));
|
||||
if ($expected_field_label !== NULL) {
|
||||
$field_label = $quickedit_entity_toolbar->find('css', '.quickedit-toolbar-label > .field');
|
||||
// Only try to find the text content of the element if it was actually
|
||||
// found; otherwise use the returned value for assertion. This helps
|
||||
// us find a more useful stack/error message from testbot instead of the
|
||||
// trimmed partial exception stack.
|
||||
if ($field_label) {
|
||||
$field_label = $field_label->getText();
|
||||
}
|
||||
$this->assertSame($expected_field_label, $field_label);
|
||||
}
|
||||
else {
|
||||
$this->assertEmpty($quickedit_entity_toolbar->find('css', '.quickedit-toolbar-label > .field'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts all EntityModels (entity instances) on the page.
|
||||
*
|
||||
* @param array $expected_entity_states
|
||||
* Must describe the expected state of all in-place editable entity
|
||||
* instances on the page.
|
||||
*
|
||||
* @see Drupal.quickedit.EntityModel
|
||||
*/
|
||||
protected function assertEntityInstanceStates(array $expected_entity_states) {
|
||||
$js_get_all_field_states_for_entity = <<<JS
|
||||
function () {
|
||||
Drupal.quickedit.collections.entities.reduce(function (result, fieldModel) { result[fieldModel.get('id')] = fieldModel.get('state'); return result; }, {})
|
||||
var entityCollection = Drupal.quickedit.collections.entities;
|
||||
return entityCollection.reduce(function (result, entityModel) {
|
||||
result[entityModel.id] = entityModel.get('state');
|
||||
return result;
|
||||
}, {});
|
||||
}()
|
||||
JS;
|
||||
$this->assertSame($expected_entity_states, $this->getSession()->evaluateScript($js_get_all_field_states_for_entity));
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts all FieldModels for the given entity instance.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The entity type ID.
|
||||
* @param int $entity_id
|
||||
* The entity ID.
|
||||
* @param int $entity_instance_id
|
||||
* The entity instance ID. (Instance on the page.)
|
||||
* @param array $expected_field_states
|
||||
* Must describe the expected state of all in-place editable fields of the
|
||||
* given entity instance.
|
||||
*/
|
||||
protected function assertEntityInstanceFieldStates($entity_type_id, $entity_id, $entity_instance_id, array $expected_field_states) {
|
||||
// Get all FieldModel states for the entity instance being asserted. This
|
||||
// ensures that $expected_field_states must describe the state of all fields
|
||||
// of the entity instance.
|
||||
$entity_page_id = $entity_type_id . '/' . $entity_id . '[' . $entity_instance_id . ']';
|
||||
$js_get_all_field_states_for_entity = <<<JS
|
||||
function () {
|
||||
var entityCollection = Drupal.quickedit.collections.entities;
|
||||
var entityModel = entityCollection.get('$entity_page_id');
|
||||
return entityModel.get('fields').reduce(function (result, fieldModel) {
|
||||
result[fieldModel.get('fieldID')] = fieldModel.get('state');
|
||||
return result;
|
||||
}, {});
|
||||
}()
|
||||
JS;
|
||||
$this->assertEquals($expected_field_states, $this->getSession()->evaluateScript($js_get_all_field_states_for_entity));
|
||||
|
||||
// Assert that those fields also have the appropriate DOM decorations.
|
||||
$expected_field_attributes = [];
|
||||
foreach ($expected_field_states as $quickedit_field_id => $expected_field_state) {
|
||||
$expected_field_attributes[$quickedit_field_id] = static::$expectedFieldStateAttributes[$expected_field_state];
|
||||
}
|
||||
$this->assertEntityInstanceFieldMarkup($entity_type_id, $entity_id, $entity_instance_id, $expected_field_attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts all in-place editable fields with markup expectations.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The entity type ID.
|
||||
* @param int $entity_id
|
||||
* The entity ID.
|
||||
* @param int $entity_instance_id
|
||||
* The entity instance ID. (Instance on the page.)
|
||||
* @param array $expected_field_attributes
|
||||
* Must describe the expected markup attributes for all given in-place
|
||||
* editable fields.
|
||||
*/
|
||||
protected function assertEntityInstanceFieldMarkup($entity_type_id, $entity_id, $entity_instance_id, array $expected_field_attributes) {
|
||||
$entity_page_id = $entity_type_id . '/' . $entity_id . '[' . $entity_instance_id . ']';
|
||||
$expected_field_attributes_json = json_encode($expected_field_attributes);
|
||||
$js_match_field_element_attributes = <<<JS
|
||||
function () {
|
||||
var expectations = $expected_field_attributes_json;
|
||||
var entityCollection = Drupal.quickedit.collections.entities;
|
||||
var entityModel = entityCollection.get('$entity_page_id');
|
||||
return entityModel.get('fields').reduce(function (result, fieldModel) {
|
||||
var fieldID = fieldModel.get('fieldID');
|
||||
var element = fieldModel.get('el');
|
||||
var matches = element.webkitMatchesSelector(expectations[fieldID]);
|
||||
result[fieldID] = matches ? matches : element.outerHTML;
|
||||
return result;
|
||||
}, {});
|
||||
}()
|
||||
JS;
|
||||
$result = $this->getSession()->evaluateScript($js_match_field_element_attributes);
|
||||
foreach ($expected_field_attributes as $quickedit_field_id => $expectation) {
|
||||
$this->assertSame(TRUE, $result[$quickedit_field_id], 'Field ' . $quickedit_field_id . ' did not match its expectation selector (' . $expectation . '), actual HTML: ' . $result[$quickedit_field_id]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates typing in a 'plain_text' in-place editor.
|
||||
*
|
||||
* @param string $css_selector
|
||||
* The CSS selector to find the DOM element (with the 'contenteditable=true'
|
||||
* attribute set), to type in.
|
||||
* @param string $text
|
||||
* The text to type.
|
||||
*
|
||||
* @see \Drupal\quickedit\Plugin\InPlaceEditor\PlainTextEditor
|
||||
*/
|
||||
protected function typeInPlainTextEditor($css_selector, $text) {
|
||||
$field = $this->getSession()->getPage()->find('css', $css_selector);
|
||||
$field->setValue(Key::END . $text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates typing in an input[type=text] inside a 'form' in-place editor.
|
||||
*
|
||||
* @param string $input_name
|
||||
* The "name" attribute of the input[type=text] to type in.
|
||||
* @param string $text
|
||||
* The text to type.
|
||||
*
|
||||
* @see \Drupal\quickedit\Plugin\InPlaceEditor\FormEditor
|
||||
*/
|
||||
protected function typeInFormEditorTextInputField($input_name, $text) {
|
||||
$input = $this->cssSelect('.quickedit-form-container > .quickedit-form[role="dialog"] form.quickedit-field-form input[type=text][name="' . $input_name . '"]')[0];
|
||||
$input->setValue($text);
|
||||
$js_simulate_user_typing = <<<JS
|
||||
function () {
|
||||
var el = document.querySelector('.quickedit-form-container > .quickedit-form[role="dialog"] form.quickedit-field-form input[name="$input_name"]');
|
||||
window.jQuery(el).trigger('formUpdated');
|
||||
}()
|
||||
JS;
|
||||
$this->getSession()->evaluateScript($js_simulate_user_typing);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,397 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\quickedit\FunctionalJavascript;
|
||||
|
||||
use Behat\Mink\Session;
|
||||
use Drupal\block_content\Entity\BlockContent;
|
||||
use Drupal\Core\Entity\Entity\EntityViewDisplay;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\filter\Entity\FilterFormat;
|
||||
use Drupal\Tests\contextual\FunctionalJavascript\ContextualLinkClickTrait;
|
||||
use Drupal\Tests\TestFileCreationTrait;
|
||||
|
||||
/**
|
||||
* Tests loading of in-place editing functionality and lazy loading of its
|
||||
* in-place editors.
|
||||
*
|
||||
* @group quickedit
|
||||
*/
|
||||
class QuickEditLoadingTest extends WebDriverTestBase {
|
||||
|
||||
use ContextualLinkClickTrait;
|
||||
|
||||
use TestFileCreationTrait {
|
||||
getTestFiles as drupalGetTestFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = [
|
||||
'contextual',
|
||||
'quickedit',
|
||||
'filter',
|
||||
'node',
|
||||
'image',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'classy';
|
||||
|
||||
/**
|
||||
* An user with permissions to create and edit articles.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $authorUser;
|
||||
|
||||
/**
|
||||
* A test node.
|
||||
*
|
||||
* @var \Drupal\node\NodeInterface
|
||||
*/
|
||||
protected $testNode;
|
||||
|
||||
/**
|
||||
* A author user with permissions to access in-place editor.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $editorUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Create a text format.
|
||||
$filtered_html_format = FilterFormat::create([
|
||||
'format' => 'filtered_html',
|
||||
'name' => 'Filtered HTML',
|
||||
'weight' => 0,
|
||||
'filters' => [],
|
||||
]);
|
||||
$filtered_html_format->save();
|
||||
|
||||
// Create a node type.
|
||||
$this->drupalCreateContentType([
|
||||
'type' => 'article',
|
||||
'name' => 'Article',
|
||||
]);
|
||||
|
||||
// Set the node type to initially not have revisions.
|
||||
// Testing with revisions will be done later.
|
||||
$node_type = NodeType::load('article');
|
||||
$node_type->setNewRevision(FALSE);
|
||||
$node_type->save();
|
||||
|
||||
// Create one node of the above node type using the above text format.
|
||||
$this->testNode = $this->drupalCreateNode([
|
||||
'type' => 'article',
|
||||
'body' => [
|
||||
0 => [
|
||||
'value' => '<p>How are you?</p>',
|
||||
'format' => 'filtered_html',
|
||||
],
|
||||
],
|
||||
'revision_log' => $this->randomString(),
|
||||
]);
|
||||
|
||||
// Create 2 users, the only difference being the ability to use in-place
|
||||
// editing
|
||||
$basic_permissions = [
|
||||
'access content',
|
||||
'create article content',
|
||||
'edit any article content',
|
||||
'use text format filtered_html',
|
||||
'access contextual links',
|
||||
];
|
||||
$this->authorUser = $this->drupalCreateUser($basic_permissions);
|
||||
$this->editorUser = $this->drupalCreateUser(array_merge($basic_permissions, ['access in-place editing']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the loading of Quick Edit with different permissions.
|
||||
*/
|
||||
public function testUserPermissions() {
|
||||
$assert = $this->assertSession();
|
||||
$this->drupalLogin($this->authorUser);
|
||||
$this->drupalGet('node/1');
|
||||
|
||||
// Library and in-place editors.
|
||||
$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.");
|
||||
|
||||
// HTML annotation and title class does not exist for users without
|
||||
// permission to in-place edit.
|
||||
$this->assertNoRaw('data-quickedit-entity-id="node/1"');
|
||||
$this->assertNoRaw('data-quickedit-field-id="node/1/body/en/full"');
|
||||
$this->assertNoFieldByXPath('//h1[contains(@class, "js-quickedit-page-title")]');
|
||||
$assert->linkNotExists('Quick edit');
|
||||
|
||||
// Tests the loading of Quick Edit when a user does have access to it.
|
||||
// Also ensures lazy loading of in-place editors works.
|
||||
$nid = $this->testNode->id();
|
||||
// There should be only one revision so far.
|
||||
$node = Node::load($nid);
|
||||
$vids = \Drupal::entityTypeManager()->getStorage('node')->revisionIds($node);
|
||||
$this->assertCount(1, $vids, 'The node has only one revision.');
|
||||
$original_log = $node->revision_log->value;
|
||||
|
||||
$this->drupalLogin($this->editorUser);
|
||||
$this->drupalGet('node/' . $nid);
|
||||
$page = $this->getSession()->getPage();
|
||||
|
||||
// Wait "Quick edit" button for node.
|
||||
$assert->waitForElement('css', '[data-quickedit-entity-id="node/' . $nid . '"] .contextual .quickedit');
|
||||
// Click by "Quick edit".
|
||||
$this->clickContextualLink('[data-quickedit-entity-id="node/' . $nid . '"]', 'Quick edit');
|
||||
// Switch to body field.
|
||||
$page->find('css', '[data-quickedit-field-id="node/' . $nid . '/body/en/full"]')->click();
|
||||
$assert->assertWaitOnAjaxRequest();
|
||||
|
||||
// Wait and update body field.
|
||||
$body_field_locator = '[name="body[0][value]"]';
|
||||
$body_text = 'Fine thanks.';
|
||||
$assert->waitForElementVisible('css', $body_field_locator)->setValue('<p>' . $body_text . '</p>');
|
||||
|
||||
// Wait and click by "Save" button after body field was changed.
|
||||
$assert->waitForElementVisible('css', '.quickedit-toolgroup.ops [type="submit"][aria-hidden="false"]')->click();
|
||||
$assert->assertWaitOnAjaxRequest();
|
||||
|
||||
$node = Node::load($nid);
|
||||
$vids = \Drupal::entityTypeManager()->getStorage('node')->revisionIds($node);
|
||||
$this->assertCount(1, $vids, 'The node has only one revision.');
|
||||
$this->assertSame($original_log, $node->revision_log->value, 'The revision log message is unchanged.');
|
||||
|
||||
// Ensure that the changes take effect.
|
||||
$assert->responseMatches("|\s*$body_text\s*|");
|
||||
|
||||
// Reload the page and check for updated body.
|
||||
$this->drupalGet('node/' . $nid);
|
||||
$assert->pageTextContains($body_text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Quick Edit does not appear for entities with pending revisions.
|
||||
*/
|
||||
public function testWithPendingRevision() {
|
||||
$this->drupalLogin($this->editorUser);
|
||||
|
||||
// Verify that the preview is loaded correctly.
|
||||
$this->drupalPostForm('node/add/article', ['title[0][value]' => 'foo'], 'Preview');
|
||||
// Verify that quickedit is not active on preview.
|
||||
$this->assertNoRaw('data-quickedit-entity-id="node/' . $this->testNode->id() . '"');
|
||||
$this->assertNoRaw('data-quickedit-field-id="node/' . $this->testNode->id() . '/title/' . $this->testNode->language()->getId() . '/full"');
|
||||
|
||||
$this->drupalGet('node/' . $this->testNode->id());
|
||||
$this->assertRaw('data-quickedit-entity-id="node/' . $this->testNode->id() . '"');
|
||||
$this->assertRaw('data-quickedit-field-id="node/' . $this->testNode->id() . '/title/' . $this->testNode->language()->getId() . '/full"');
|
||||
|
||||
// Wait for the page to completely load before making any changes to the
|
||||
// node. This allows Quick Edit to fetch the metadata without causing
|
||||
// database locks on SQLite.
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$this->testNode->title = 'Updated node';
|
||||
$this->testNode->setNewRevision(TRUE);
|
||||
$this->testNode->isDefaultRevision(FALSE);
|
||||
$this->testNode->save();
|
||||
|
||||
$this->drupalGet('node/' . $this->testNode->id());
|
||||
$this->assertNoRaw('data-quickedit-entity-id="node/' . $this->testNode->id() . '"');
|
||||
$this->assertNoRaw('data-quickedit-field-id="node/' . $this->testNode->id() . '/title/' . $this->testNode->language()->getId() . '/full"');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the loading of Quick Edit for the title base field.
|
||||
*/
|
||||
public function testTitleBaseField() {
|
||||
$page = $this->getSession()->getPage();
|
||||
$assert = $this->assertSession();
|
||||
$nid = $this->testNode->id();
|
||||
|
||||
$this->drupalLogin($this->editorUser);
|
||||
$this->drupalGet('node/' . $nid);
|
||||
|
||||
// Wait "Quick edit" button for node.
|
||||
$assert->waitForElement('css', '[data-quickedit-entity-id="node/' . $nid . '"] .contextual .quickedit');
|
||||
// Click by "Quick edit".
|
||||
$this->clickContextualLink('[data-quickedit-entity-id="node/' . $nid . '"]', 'Quick edit');
|
||||
// Switch to title field.
|
||||
$page->find('css', '[data-quickedit-field-id="node/' . $nid . '/title/en/full"]')->click();
|
||||
$assert->assertWaitOnAjaxRequest();
|
||||
|
||||
// Wait and update title field.
|
||||
$field_locator = '.field--name-title';
|
||||
$text_new = 'Obligatory question';
|
||||
$assert->waitForElementVisible('css', $field_locator)->setValue($text_new);
|
||||
|
||||
// Wait and click by "Save" button after title field was changed.
|
||||
$this->assertSession()->waitForElementVisible('css', '.quickedit-toolgroup.ops [type="submit"][aria-hidden="false"]')->click();
|
||||
$assert->assertWaitOnAjaxRequest();
|
||||
|
||||
// Ensure that the changes take effect.
|
||||
$assert->responseMatches("|\s*$text_new\s*|");
|
||||
|
||||
// Reload the page and check for updated title.
|
||||
$this->drupalGet('node/' . $nid);
|
||||
$assert->pageTextContains($text_new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that Quick Edit doesn't make fields rendered with display options
|
||||
* editable.
|
||||
*/
|
||||
public function testDisplayOptions() {
|
||||
$node = Node::load('1');
|
||||
$display_settings = [
|
||||
'label' => 'inline',
|
||||
];
|
||||
$build = $node->body->view($display_settings);
|
||||
$output = \Drupal::service('renderer')->renderRoot($build);
|
||||
$this->assertStringNotContainsString('data-quickedit-field-id', $output, 'data-quickedit-field-id attribute not added when rendering field using dynamic display options.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests Quick Edit on a node that was concurrently edited on the full node
|
||||
* form.
|
||||
*/
|
||||
public function testConcurrentEdit() {
|
||||
$nid = $this->testNode->id();
|
||||
$this->drupalLogin($this->authorUser);
|
||||
|
||||
// Open the edit page in the default session.
|
||||
$this->drupalGet('node/' . $nid . '/edit');
|
||||
|
||||
// Switch to a concurrent session and save a quick edit change.
|
||||
// We need to do some bookkeeping to keep track of the logged in user.
|
||||
$logged_in_user = $this->loggedInUser;
|
||||
$this->loggedInUser = FALSE;
|
||||
// Register a session to preform concurrent editing.
|
||||
$driver = $this->getDefaultDriverInstance();
|
||||
$session = new Session($driver);
|
||||
$this->mink->registerSession('concurrent', $session);
|
||||
$this->mink->setDefaultSessionName('concurrent');
|
||||
$this->initFrontPage();
|
||||
$this->drupalLogin($this->editorUser);
|
||||
$this->drupalGet('node/' . $nid);
|
||||
|
||||
$assert = $this->assertSession();
|
||||
$page = $this->getSession()->getPage();
|
||||
|
||||
// Wait "Quick edit" button for node.
|
||||
$assert->waitForElement('css', '[data-quickedit-entity-id="node/' . $nid . '"] .contextual .quickedit');
|
||||
// Click by "Quick edit".
|
||||
$this->clickContextualLink('[data-quickedit-entity-id="node/' . $nid . '"]', 'Quick edit');
|
||||
// Switch to body field.
|
||||
$page->find('css', '[data-quickedit-field-id="node/' . $nid . '/body/en/full"]')->click();
|
||||
$assert->assertWaitOnAjaxRequest();
|
||||
|
||||
// Wait and update body field.
|
||||
$body_field_locator = '[name="body[0][value]"]';
|
||||
$body_text = 'Fine thanks.';
|
||||
$assert->waitForElementVisible('css', $body_field_locator)->setValue('<p>' . $body_text . '</p>');
|
||||
|
||||
// Wait and click by "Save" button after body field was changed.
|
||||
$assert->waitForElementVisible('css', '.quickedit-toolgroup.ops [type="submit"][aria-hidden="false"]')->click();
|
||||
$assert->assertWaitOnAjaxRequest();
|
||||
|
||||
// Ensure that the changes take effect.
|
||||
$assert->responseMatches("|\s*$body_text\s*|");
|
||||
|
||||
// Switch back to the default session.
|
||||
$this->mink->setDefaultSessionName('default');
|
||||
$this->loggedInUser = $logged_in_user;
|
||||
// Ensure different save timestamps for field editing.
|
||||
sleep(2);
|
||||
$this->drupalPostForm(NULL, ['body[0][value]' => '<p>Concurrent edit!</p>'], 'Save');
|
||||
|
||||
$this->getSession()->getPage()->hasContent('The content has either been modified by another user, or you have already submitted modifications. As a result, your changes cannot be saved.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that Quick Edit's data- attributes are present for content blocks.
|
||||
*/
|
||||
public function testContentBlock() {
|
||||
\Drupal::service('module_installer')->install(['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->drupalLogin($this->editorUser);
|
||||
$this->drupalGet('');
|
||||
$this->assertRaw('data-quickedit-entity-id="block_content/1"');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that Quick Edit can handle an image field.
|
||||
*/
|
||||
public function testImageField() {
|
||||
$page = $this->getSession()->getPage();
|
||||
$assert = $this->assertSession();
|
||||
|
||||
// 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();
|
||||
\Drupal::service('entity_display.repository')->getFormDisplay('node', 'article', 'default')
|
||||
->setComponent('field_image', [
|
||||
'type' => 'image_image',
|
||||
])
|
||||
->save();
|
||||
$display = EntityViewDisplay::load('node.article.default');
|
||||
$display->setComponent('field_image', [
|
||||
'type' => 'image',
|
||||
])->save();
|
||||
|
||||
// Add an image to the node.
|
||||
$this->drupalLogin($this->editorUser);
|
||||
$this->drupalGet('node/1/edit');
|
||||
$image = $this->drupalGetTestFiles('image')[0];
|
||||
$image_path = $this->container->get('file_system')->realpath($image->uri);
|
||||
$page->attachFileToField('files[field_image_0]', $image_path);
|
||||
$alt_field = $assert->waitForField('field_image[0][alt]');
|
||||
$this->assertNotEmpty($alt_field);
|
||||
$this->drupalPostForm(NULL, [
|
||||
'field_image[0][alt]' => 'Vivamus aliquet elit',
|
||||
], t('Save'));
|
||||
|
||||
// The image field form should load normally.
|
||||
// Wait "Quick edit" button for node.
|
||||
$assert->waitForElement('css', '[data-quickedit-entity-id="node/1"] .contextual .quickedit');
|
||||
// Click by "Quick edit".
|
||||
$this->clickContextualLink('[data-quickedit-entity-id="node/1"]', 'Quick edit');
|
||||
// Switch to body field.
|
||||
$assert->waitForElement('css', '[data-quickedit-field-id="node/1/field_image/en/full"]')->click();
|
||||
$assert->assertWaitOnAjaxRequest();
|
||||
|
||||
$field_locator = '.field--name-field-image';
|
||||
$assert->waitForElementVisible('css', $field_locator);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\quickedit\Kernel;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\quickedit\EditorSelector;
|
||||
|
||||
/**
|
||||
* Tests in-place field editor selection.
|
||||
*
|
||||
* @group quickedit
|
||||
*/
|
||||
class EditorSelectionTest extends QuickEditTestBase {
|
||||
|
||||
/**
|
||||
* The manager for editor plugins.
|
||||
*
|
||||
* @var \Drupal\Component\Plugin\PluginManagerInterface
|
||||
*/
|
||||
protected $editorManager;
|
||||
|
||||
/**
|
||||
* The editor selector object to be tested.
|
||||
*
|
||||
* @var \Drupal\quickedit\EditorSelectorInterface
|
||||
*/
|
||||
protected $editorSelector;
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->editorManager = $this->container->get('plugin.manager.quickedit.editor');
|
||||
$this->editorSelector = new EditorSelector($this->editorManager, $this->container->get('plugin.manager.field.formatter'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the in-place editor that Quick Edit selects.
|
||||
*/
|
||||
protected function getSelectedEditor($entity_id, $field_name, $view_mode = 'default') {
|
||||
$storage = $this->container->get('entity_type.manager')->getStorage('entity_test');
|
||||
$storage->resetCache([$entity_id]);
|
||||
$entity = $storage->load($entity_id);
|
||||
$items = $entity->get($field_name);
|
||||
$options = \Drupal::service('entity_display.repository')
|
||||
->getViewDisplay('entity_test', 'entity_test', $view_mode)
|
||||
->getComponent($field_name);
|
||||
return $this->editorSelector->getEditor($options['type'], $items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a string (plain text) field, with cardinality 1 and >1.
|
||||
*/
|
||||
public function testText() {
|
||||
$field_name = 'field_text';
|
||||
$this->createFieldWithStorage(
|
||||
$field_name, 'string', 1, 'Simple text field',
|
||||
// Instance settings.
|
||||
[],
|
||||
// Widget type & settings.
|
||||
'string_textfield',
|
||||
['size' => 42],
|
||||
// 'default' formatter type & settings.
|
||||
'string',
|
||||
[]
|
||||
);
|
||||
|
||||
// Create an entity with values for this text field.
|
||||
$entity = EntityTest::create();
|
||||
$entity->{$field_name}->value = 'Hello, world!';
|
||||
$entity->save();
|
||||
|
||||
// With cardinality 1.
|
||||
$this->assertEqual('plain_text', $this->getSelectedEditor($entity->id(), $field_name), "With cardinality 1, the 'plain_text' editor is selected.");
|
||||
|
||||
// With cardinality >1
|
||||
$this->fields->field_text_field_storage->setCardinality(2);
|
||||
$this->fields->field_text_field_storage->save();
|
||||
$this->assertEqual('form', $this->getSelectedEditor($entity->id(), $field_name), "With cardinality >1, the 'form' editor is selected.");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a textual field, with text filtering, with cardinality 1 and >1,
|
||||
* always with an Editor plugin present that supports textual fields with text
|
||||
* filtering, but with varying text format compatibility.
|
||||
*/
|
||||
public function testTextWysiwyg() {
|
||||
// Enable edit_test module so that the 'wysiwyg' editor becomes available.
|
||||
$this->enableModules(['quickedit_test']);
|
||||
$this->editorManager = $this->container->get('plugin.manager.quickedit.editor');
|
||||
$this->editorSelector = new EditorSelector($this->editorManager, $this->container->get('plugin.manager.field.formatter'));
|
||||
|
||||
$field_name = 'field_textarea';
|
||||
$this->createFieldWithStorage(
|
||||
$field_name, 'text', 1, 'Long text field',
|
||||
// Instance settings.
|
||||
[],
|
||||
// Widget type & settings.
|
||||
'text_textarea',
|
||||
['size' => 42],
|
||||
// 'default' formatter type & settings.
|
||||
'text_default',
|
||||
[]
|
||||
);
|
||||
|
||||
// Create an entity with values for this text field.
|
||||
$entity = EntityTest::create();
|
||||
$entity->{$field_name}->value = 'Hello, world!';
|
||||
$entity->{$field_name}->format = 'filtered_html';
|
||||
$entity->save();
|
||||
|
||||
// Editor selection w/ cardinality 1, text format w/o associated text editor.
|
||||
$this->assertEqual('form', $this->getSelectedEditor($entity->id(), $field_name), "With cardinality 1, and the filtered_html text format, the 'form' editor is selected.");
|
||||
|
||||
// Editor selection w/ cardinality 1, text format w/ associated text editor.
|
||||
$entity->{$field_name}->format = 'full_html';
|
||||
$entity->save();
|
||||
$this->assertEqual('wysiwyg', $this->getSelectedEditor($entity->id(), $field_name), "With cardinality 1, and the full_html text format, the 'wysiwyg' editor is selected.");
|
||||
|
||||
// Editor selection with text field, cardinality >1.
|
||||
$this->fields->field_textarea_field_storage->setCardinality(2);
|
||||
$this->fields->field_textarea_field_storage->save();
|
||||
$this->assertEqual('form', $this->getSelectedEditor($entity->id(), $field_name), "With cardinality >1, and both items using the full_html text format, the 'form' editor is selected.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a number field, with cardinality 1 and >1.
|
||||
*/
|
||||
public function testNumber() {
|
||||
$field_name = 'field_nr';
|
||||
$this->createFieldWithStorage(
|
||||
$field_name, 'integer', 1, 'Simple number field',
|
||||
// Instance settings.
|
||||
[],
|
||||
// Widget type & settings.
|
||||
'number',
|
||||
[],
|
||||
// 'default' formatter type & settings.
|
||||
'number_integer',
|
||||
[]
|
||||
);
|
||||
|
||||
// Create an entity with values for this text field.
|
||||
$entity = EntityTest::create();
|
||||
$entity->{$field_name}->value = 42;
|
||||
$entity->save();
|
||||
|
||||
// Editor selection with cardinality 1.
|
||||
$this->assertEqual('form', $this->getSelectedEditor($entity->id(), $field_name), "With cardinality 1, the 'form' editor is selected.");
|
||||
|
||||
// Editor selection with cardinality >1.
|
||||
$this->fields->field_nr_field_storage->setCardinality(2);
|
||||
$this->fields->field_nr_field_storage->save();
|
||||
$this->assertEqual('form', $this->getSelectedEditor($entity->id(), $field_name), "With cardinality >1, the 'form' editor is selected.");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\quickedit\Kernel;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\quickedit\EditorSelector;
|
||||
use Drupal\quickedit\MetadataGenerator;
|
||||
use Drupal\quickedit_test\MockQuickEditEntityFieldAccessCheck;
|
||||
use Drupal\filter\Entity\FilterFormat;
|
||||
|
||||
/**
|
||||
* Tests in-place field editing metadata.
|
||||
*
|
||||
* @group quickedit
|
||||
*/
|
||||
class MetadataGeneratorTest extends QuickEditTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['quickedit_test'];
|
||||
|
||||
/**
|
||||
* The manager for editor plugins.
|
||||
*
|
||||
* @var \Drupal\Component\Plugin\PluginManagerInterface
|
||||
*/
|
||||
protected $editorManager;
|
||||
|
||||
/**
|
||||
* The metadata generator object to be tested.
|
||||
*
|
||||
* @var \Drupal\quickedit\MetadataGeneratorInterface
|
||||
*/
|
||||
protected $metadataGenerator;
|
||||
|
||||
/**
|
||||
* The editor selector object to be used by the metadata generator object.
|
||||
*
|
||||
* @var \Drupal\quickedit\EditorSelectorInterface
|
||||
*/
|
||||
protected $editorSelector;
|
||||
|
||||
/**
|
||||
* The access checker object to be used by the metadata generator object.
|
||||
*
|
||||
* @var \Drupal\quickedit\Access\QuickEditEntityFieldAccessCheckInterface
|
||||
*/
|
||||
protected $accessChecker;
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->editorManager = $this->container->get('plugin.manager.quickedit.editor');
|
||||
$this->accessChecker = new MockQuickEditEntityFieldAccessCheck();
|
||||
$this->editorSelector = new EditorSelector($this->editorManager, $this->container->get('plugin.manager.field.formatter'));
|
||||
$this->metadataGenerator = new MetadataGenerator($this->accessChecker, $this->editorSelector, $this->editorManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a simple entity type, with two different simple fields.
|
||||
*/
|
||||
public function testSimpleEntityType() {
|
||||
$field_1_name = 'field_text';
|
||||
$field_1_label = 'Plain text field';
|
||||
$this->createFieldWithStorage(
|
||||
$field_1_name, 'string', 1, $field_1_label,
|
||||
// Instance settings.
|
||||
[],
|
||||
// Widget type & settings.
|
||||
'string_textfield',
|
||||
['size' => 42],
|
||||
// 'default' formatter type & settings.
|
||||
'string',
|
||||
[]
|
||||
);
|
||||
$field_2_name = 'field_nr';
|
||||
$field_2_label = 'Simple number field';
|
||||
$this->createFieldWithStorage(
|
||||
$field_2_name, 'integer', 1, $field_2_label,
|
||||
// Instance settings.
|
||||
[],
|
||||
// Widget type & settings.
|
||||
'number',
|
||||
[],
|
||||
// 'default' formatter type & settings.
|
||||
'number_integer',
|
||||
[]
|
||||
);
|
||||
|
||||
// Create an entity with values for this text field.
|
||||
$entity = EntityTest::create();
|
||||
$entity->{$field_1_name}->value = 'Test';
|
||||
$entity->{$field_2_name}->value = 42;
|
||||
$entity->save();
|
||||
$entity = EntityTest::load($entity->id());
|
||||
|
||||
// Verify metadata for field 1.
|
||||
$items_1 = $entity->get($field_1_name);
|
||||
$metadata_1 = $this->metadataGenerator->generateFieldMetadata($items_1, 'default');
|
||||
$expected_1 = [
|
||||
'access' => TRUE,
|
||||
'label' => 'Plain text field',
|
||||
'editor' => 'plain_text',
|
||||
];
|
||||
$this->assertEqual($expected_1, $metadata_1, 'The correct metadata is generated for the first field.');
|
||||
|
||||
// Verify metadata for field 2.
|
||||
$items_2 = $entity->get($field_2_name);
|
||||
$metadata_2 = $this->metadataGenerator->generateFieldMetadata($items_2, 'default');
|
||||
$expected_2 = [
|
||||
'access' => TRUE,
|
||||
'label' => 'Simple number field',
|
||||
'editor' => 'form',
|
||||
];
|
||||
$this->assertEqual($expected_2, $metadata_2, 'The correct metadata is generated for the second field.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests a field whose associated in-place editor generates custom metadata.
|
||||
*/
|
||||
public function testEditorWithCustomMetadata() {
|
||||
$this->editorManager = $this->container->get('plugin.manager.quickedit.editor');
|
||||
$this->editorSelector = new EditorSelector($this->editorManager, $this->container->get('plugin.manager.field.formatter'));
|
||||
$this->metadataGenerator = new MetadataGenerator($this->accessChecker, $this->editorSelector, $this->editorManager);
|
||||
|
||||
$this->editorManager = $this->container->get('plugin.manager.quickedit.editor');
|
||||
$this->editorSelector = new EditorSelector($this->editorManager, $this->container->get('plugin.manager.field.formatter'));
|
||||
$this->metadataGenerator = new MetadataGenerator($this->accessChecker, $this->editorSelector, $this->editorManager);
|
||||
|
||||
// Create a rich text field.
|
||||
$field_name = 'field_rich';
|
||||
$field_label = 'Rich text field';
|
||||
$this->createFieldWithStorage(
|
||||
$field_name, 'text', 1, $field_label,
|
||||
// Instance settings.
|
||||
[],
|
||||
// Widget type & settings.
|
||||
'text_textfield',
|
||||
['size' => 42],
|
||||
// 'default' formatter type & settings.
|
||||
'text_default',
|
||||
[]
|
||||
);
|
||||
|
||||
// Create a text format.
|
||||
$full_html_format = FilterFormat::create([
|
||||
'format' => 'full_html',
|
||||
'name' => 'Full HTML',
|
||||
'weight' => 1,
|
||||
'filters' => [
|
||||
'filter_htmlcorrector' => ['status' => 1],
|
||||
],
|
||||
]);
|
||||
$full_html_format->save();
|
||||
|
||||
// Create an entity with values for this rich text field.
|
||||
$entity = EntityTest::create();
|
||||
$entity->{$field_name}->value = 'Test';
|
||||
$entity->{$field_name}->format = 'full_html';
|
||||
$entity->save();
|
||||
$entity = EntityTest::load($entity->id());
|
||||
|
||||
// Verify metadata.
|
||||
$items = $entity->get($field_name);
|
||||
$metadata = $this->metadataGenerator->generateFieldMetadata($items, 'default');
|
||||
$expected = [
|
||||
'access' => TRUE,
|
||||
'label' => 'Rich text field',
|
||||
'editor' => 'wysiwyg',
|
||||
'custom' => [
|
||||
'format' => 'full_html',
|
||||
],
|
||||
];
|
||||
$this->assertEqual($expected, $metadata, 'The correct metadata (including custom metadata) is generated.');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\quickedit\Kernel;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
|
||||
use Drupal\Tests\node\Traits\NodeCreationTrait;
|
||||
use Drupal\Tests\user\Traits\UserCreationTrait;
|
||||
|
||||
/**
|
||||
* Tests loading of in-place editing functionality and lazy loading of its
|
||||
* in-place editors.
|
||||
*
|
||||
* @group quickedit
|
||||
*/
|
||||
class QuickEditLoadingTest extends KernelTestBase {
|
||||
|
||||
use NodeCreationTrait;
|
||||
use UserCreationTrait;
|
||||
use ContentTypeCreationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'user',
|
||||
'system',
|
||||
'field',
|
||||
'node',
|
||||
'text',
|
||||
'filter',
|
||||
'contextual',
|
||||
'quickedit',
|
||||
];
|
||||
|
||||
/**
|
||||
* A user with permissions to access in-place editor.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $editorUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->installSchema('system', 'sequences');
|
||||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('node');
|
||||
$this->installConfig(['field', 'filter', 'node']);
|
||||
|
||||
// Create a Content type and one test node.
|
||||
$this->createContentType(['type' => 'page']);
|
||||
$this->createNode();
|
||||
|
||||
$this->editorUser = $this->createUser([
|
||||
'access content',
|
||||
'create page content',
|
||||
'edit any page content',
|
||||
'access contextual links',
|
||||
'access in-place editing',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that Quick Edit doesn't make fields rendered with display options
|
||||
* editable.
|
||||
*/
|
||||
public function testDisplayOptions() {
|
||||
$node = Node::load(1);
|
||||
$renderer = $this->container->get('renderer');
|
||||
$this->container->get('current_user')->setAccount($this->editorUser);
|
||||
|
||||
$build = $node->body->view(['label' => 'inline']);
|
||||
$this->setRawContent($renderer->renderRoot($build));
|
||||
$elements = $this->xpath('//div[@data-quickedit-field-id]');
|
||||
$this->assertFalse(!empty($elements), 'data-quickedit-field-id attribute not added when rendering field using dynamic display options.');
|
||||
|
||||
$build = $node->body->view('default');
|
||||
$this->setRawContent($renderer->renderRoot($build));
|
||||
$elements = $this->xpath('//div[@data-quickedit-field-id="node/1/body/en/default"]');
|
||||
$this->assertTrue(!empty($elements), 'Body with data-quickedit-field-id attribute found.');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\quickedit\Kernel;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
||||
/**
|
||||
* Base class for testing Quick Edit functionality.
|
||||
*/
|
||||
abstract class QuickEditTestBase extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = [
|
||||
'system',
|
||||
'entity_test',
|
||||
'field',
|
||||
'field_test',
|
||||
'filter',
|
||||
'user',
|
||||
'text',
|
||||
'quickedit',
|
||||
];
|
||||
|
||||
/**
|
||||
* Bag of created fields.
|
||||
*
|
||||
* Allows easy access to test field names/IDs/objects via:
|
||||
* - $this->fields->{$field_name}_field_storage
|
||||
* - $this->fields->{$field_name}_instance
|
||||
*
|
||||
* @see \Drupal\quickedit\Tests\QuickEditTestBase::createFieldWithStorage()
|
||||
*
|
||||
* @var \ArrayObject
|
||||
*/
|
||||
protected $fields;
|
||||
|
||||
/**
|
||||
* Sets the default field storage backend for fields created during tests.
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->fields = new \ArrayObject([], \ArrayObject::ARRAY_AS_PROPS);
|
||||
|
||||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('entity_test');
|
||||
$this->installConfig(['field', 'filter']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a field.
|
||||
*
|
||||
* @param string $field_name
|
||||
* The field name.
|
||||
* @param string $type
|
||||
* The field type.
|
||||
* @param int $cardinality
|
||||
* The field's cardinality.
|
||||
* @param string $label
|
||||
* The field's label (used everywhere: widget label, formatter label).
|
||||
* @param array $field_settings
|
||||
* @param string $widget_type
|
||||
* The widget type.
|
||||
* @param array $widget_settings
|
||||
* The widget settings.
|
||||
* @param string $formatter_type
|
||||
* The formatter type.
|
||||
* @param array $formatter_settings
|
||||
* The formatter settings.
|
||||
*/
|
||||
protected function createFieldWithStorage($field_name, $type, $cardinality, $label, $field_settings, $widget_type, $widget_settings, $formatter_type, $formatter_settings) {
|
||||
$field_storage = $field_name . '_field_storage';
|
||||
$this->fields->$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => $type,
|
||||
'cardinality' => $cardinality,
|
||||
]);
|
||||
$this->fields->$field_storage->save();
|
||||
|
||||
$field = $field_name . '_field';
|
||||
$this->fields->$field = FieldConfig::create([
|
||||
'field_storage' => $this->fields->$field_storage,
|
||||
'bundle' => 'entity_test',
|
||||
'label' => $label,
|
||||
'description' => $label,
|
||||
'weight' => mt_rand(0, 127),
|
||||
'settings' => $field_settings,
|
||||
]);
|
||||
$this->fields->$field->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
$display_repository->getFormDisplay('entity_test', 'entity_test')
|
||||
->setComponent($field_name, [
|
||||
'type' => $widget_type,
|
||||
'settings' => $widget_settings,
|
||||
])
|
||||
->save();
|
||||
|
||||
$display_repository->getViewDisplay('entity_test', 'entity_test')
|
||||
->setComponent($field_name, [
|
||||
'label' => 'above',
|
||||
'type' => $formatter_type,
|
||||
'settings' => $formatter_settings,
|
||||
])
|
||||
->save();
|
||||
}
|
||||
|
||||
}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\Tests\quickedit\Unit\Access;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Cache\Context\CacheContextsManager;
|
||||
use Drupal\Core\DependencyInjection\Container;
|
||||
use Drupal\quickedit\Access\QuickEditEntityFieldAccessCheck;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\quickedit\Access\QuickEditEntityFieldAccessCheck
|
||||
* @group Access
|
||||
* @group quickedit
|
||||
*/
|
||||
class QuickEditEntityFieldAccessCheckTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* The tested access checker.
|
||||
*
|
||||
* @var \Drupal\quickedit\Access\QuickEditEntityFieldAccessCheck
|
||||
*/
|
||||
protected $editAccessCheck;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
$this->editAccessCheck = new QuickEditEntityFieldAccessCheck();
|
||||
|
||||
$cache_contexts_manager = $this->prophesize(CacheContextsManager::class);
|
||||
$cache_contexts_manager->assertValidTokens()->willReturn(TRUE);
|
||||
$cache_contexts_manager->reveal();
|
||||
$container = new Container();
|
||||
$container->set('cache_contexts_manager', $cache_contexts_manager);
|
||||
\Drupal::setContainer($container);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides test data for testAccess().
|
||||
*
|
||||
* @see \Drupal\Tests\edit\Unit\quickedit\Access\QuickEditEntityFieldAccessCheckTest::testAccess()
|
||||
*/
|
||||
public function providerTestAccess() {
|
||||
$data = [];
|
||||
$data[] = [TRUE, TRUE, AccessResult::allowed()];
|
||||
$data[] = [FALSE, TRUE, AccessResult::neutral()];
|
||||
$data[] = [TRUE, FALSE, AccessResult::neutral()];
|
||||
$data[] = [FALSE, FALSE, AccessResult::neutral()];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the method for checking access to routes.
|
||||
*
|
||||
* @param bool $entity_is_editable
|
||||
* Whether the subject entity is editable.
|
||||
* @param bool $field_storage_is_accessible
|
||||
* Whether the user has access to the field storage entity.
|
||||
* @param \Drupal\Core\Access\AccessResult $expected_result
|
||||
* The expected result of the access call.
|
||||
*
|
||||
* @dataProvider providerTestAccess
|
||||
*/
|
||||
public function testAccess($entity_is_editable, $field_storage_is_accessible, AccessResult $expected_result) {
|
||||
$entity = $this->createMockEntity();
|
||||
$entity->expects($this->any())
|
||||
->method('access')
|
||||
->willReturn(AccessResult::allowedIf($entity_is_editable)->cachePerPermissions());
|
||||
|
||||
$field_storage = $this->createMock('Drupal\field\FieldStorageConfigInterface');
|
||||
$field_storage->expects($this->any())
|
||||
->method('access')
|
||||
->willReturn(AccessResult::allowedIf($field_storage_is_accessible));
|
||||
|
||||
$expected_result->cachePerPermissions();
|
||||
|
||||
$field_name = 'valid';
|
||||
$entity_with_field = clone $entity;
|
||||
$entity_with_field->expects($this->any())
|
||||
->method('get')
|
||||
->with($field_name)
|
||||
->will($this->returnValue($field_storage));
|
||||
$entity_with_field->expects($this->once())
|
||||
->method('hasTranslation')
|
||||
->with(LanguageInterface::LANGCODE_NOT_SPECIFIED)
|
||||
->will($this->returnValue(TRUE));
|
||||
|
||||
$account = $this->createMock('Drupal\Core\Session\AccountInterface');
|
||||
$access = $this->editAccessCheck->access($entity_with_field, $field_name, LanguageInterface::LANGCODE_NOT_SPECIFIED, $account);
|
||||
$this->assertEquals($expected_result, $access);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests checking access to routes that result in AccessResult::isForbidden().
|
||||
*
|
||||
* @dataProvider providerTestAccessForbidden
|
||||
*/
|
||||
public function testAccessForbidden($field_name, $langcode) {
|
||||
$account = $this->createMock('Drupal\Core\Session\AccountInterface');
|
||||
$entity = $this->createMockEntity();
|
||||
$this->assertEquals(AccessResult::forbidden(), $this->editAccessCheck->access($entity, $field_name, $langcode, $account));
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides test data for testAccessForbidden.
|
||||
*/
|
||||
public function providerTestAccessForbidden() {
|
||||
$data = [];
|
||||
// Tests the access method without a field_name.
|
||||
$data[] = [NULL, LanguageInterface::LANGCODE_NOT_SPECIFIED];
|
||||
// Tests the access method with a non-existent field.
|
||||
$data[] = ['not_valid', LanguageInterface::LANGCODE_NOT_SPECIFIED];
|
||||
// Tests the access method without a langcode.
|
||||
$data[] = ['valid', NULL];
|
||||
// Tests the access method with an invalid langcode.
|
||||
$data[] = ['valid', 'xx-lolspeak'];
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a mock entity.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
protected function createMockEntity() {
|
||||
$entity = $this->getMockBuilder('Drupal\entity_test\Entity\EntityTest')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$entity->expects($this->any())
|
||||
->method('hasTranslation')
|
||||
->will($this->returnValueMap([
|
||||
[LanguageInterface::LANGCODE_NOT_SPECIFIED, TRUE],
|
||||
['xx-lolspeak', FALSE],
|
||||
]));
|
||||
$entity->expects($this->any())
|
||||
->method('hasField')
|
||||
->will($this->returnValueMap([
|
||||
['valid', TRUE],
|
||||
['not_valid', FALSE],
|
||||
]));
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user