diff --git a/core/modules/media/js/plugins/drupalmedia/plugin.es6.js b/core/modules/media/js/plugins/drupalmedia/plugin.es6.js
index 81f5561fab..25103e92de 100644
--- a/core/modules/media/js/plugins/drupalmedia/plugin.es6.js
+++ b/core/modules/media/js/plugins/drupalmedia/plugin.es6.js
@@ -160,12 +160,28 @@
},
data(event) {
+ // In the CKEditor Widget, we use `hasCaption` to track whether
+ // the embedded media has a caption or not. The filter on the other
+ // hand simply uses an empty or non-existing `data-caption`
+ // attribute. So some conversion work is necessary to allow an
+ // empty caption with placeholder text.
+ if (!this.data.hasCaption) {
+ delete this.data.attributes['data-caption'];
+ } else if (
+ this.data.hasCaption &&
+ this.oldData &&
+ !this.oldData.hasCaption
+ ) {
+ this.data.attributes['data-caption'] = ' ';
+ }
+
if (this._previewNeedsServerSideUpdate()) {
editor.fire('lockSnapshot');
this._tearDownDynamicEditables();
this._loadPreview(widget => {
widget._setUpDynamicEditables();
+ widget._setUpButtons();
editor.fire('unlockSnapshot');
});
}
@@ -180,7 +196,6 @@
.getParent()
.addClass(`align-${this.data.attributes['data-align']}`);
}
-
// Track the previous state to allow checking if preview needs
// server side update.
this.oldData = CKEDITOR.tools.clone(this.data);
@@ -224,7 +239,125 @@
childList: true,
subtree: true,
});
+ // Some browsers will add a
tag to a newly created DOM
+ // element with no content. Remove this
if it is the only
+ // thing in the caption. Our placeholder support requires the
+ // element be entirely empty. See filter-caption.css.
+ // @see core/modules/ckeditor/js/plugins/drupalimagecaption/plugin.es6.js
+ if (
+ captionEditable.$.childNodes.length === 1 &&
+ captionEditable.$.childNodes.item(0).nodeName === 'BR'
+ ) {
+ captionEditable.$.removeChild(
+ captionEditable.$.childNodes.item(0),
+ );
+ }
+ }
+ },
+
+ /**
+ * Injects HTML for buttons into the preview that was just loaded.
+ */
+ _setUpButtons() {
+ // No buttons for missing media.
+ if (this.element.findOne('.media-embed-error')) {
+ return;
}
+
+ /**
+ * Determine if element is a node.
+ *
+ * Returns true if element.nodeType is equal to 1, meaning a node
+ * element and not a non-node element (such as text).
+ *
+ * @see https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR.html#property-NODE_ELEMENT
+ *
+ * @return {bool}
+ */
+ const isElementNode = function(n) {
+ return n.type === CKEDITOR.NODE_ELEMENT;
+ };
+
+ // Find the actual embedded media in the DOM.
+ const embeddedMediaContainer = this.data.hasCaption
+ ? this.element.findOne('figure')
+ : this.element;
+ let embeddedMedia = embeddedMediaContainer.getFirst(isElementNode);
+ // If there is a link, the top-level element is the a tag,
+ // and the embedded media will be within the a tag.
+ if (this.data.link) {
+ embeddedMedia = embeddedMedia.getFirst(isElementNode);
+ }
+ // To allow the edit and remove buttons to be absolutely positioned
+ // the parent element must be position relative.
+ embeddedMedia.setStyle('position', 'relative');
+ const editButton = CKEDITOR.dom.element.createFromHtml(
+ `${Drupal.t(
+ 'Edit media',
+ )}`,
+ );
+ embeddedMedia.getFirst().insertBeforeMe(editButton);
+
+ const deleteButton = CKEDITOR.dom.element.createFromHtml(
+ `${Drupal.t(
+ 'Remove media',
+ )}`,
+ );
+ embeddedMedia.getFirst().insertBeforeMe(deleteButton);
+
+ // Make the buttons do things.
+ const widget = this;
+ this.element
+ .findOne('.media-library-item__edit')
+ .on('click', event => {
+ const saveCallback = function(values) {
+ event.cancel();
+ editor.fire('saveSnapshot');
+ if (values.hasOwnProperty('attributes')) {
+ CKEDITOR.tools.extend(
+ values.attributes,
+ widget.data.attributes,
+ );
+ // Allow the dialog to delete attributes by setting them
+ // to `false` or `none`. For example: `alt`.
+ Object.keys(values.attributes).forEach(prop => {
+ if (
+ values.attributes[prop] === false ||
+ (prop === 'data-align' &&
+ values.attributes[prop] === 'none')
+ ) {
+ delete values.attributes[prop];
+ }
+ });
+ }
+ widget.setData({
+ attributes: values.attributes,
+ // Coerces to boolean. If it was falsey (e.g. 0, null,
+ // undefined, etc.), it will be false, otherwise, true.
+ // @see https://stackoverflow.com/a/10597474/1214689
+ hasCaption: !!values.hasCaption,
+ });
+ editor.fire('saveSnapshot');
+ };
+
+ Drupal.ckeditor.openDialog(
+ editor,
+ Drupal.url(
+ `editor/dialog/media/${editor.config.drupal.format}`,
+ ),
+ widget.data,
+ saveCallback,
+ {},
+ );
+ });
+ this.element
+ .findOne('.media-library-item__remove')
+ .on('click', event => {
+ event.cancel();
+ editor.fire('saveSnapshot');
+ widget.repository.del(widget);
+ editor.fire('saveSnapshot');
+ });
},
_tearDownDynamicEditables() {
@@ -237,7 +370,7 @@
/**
* Determines if the preview needs to be re-rendered by the server.
*
- * @returns {boolean}
+ * @return {boolean}
*/
_previewNeedsServerSideUpdate() {
// When the widget is first loading, it of course needs to still get a preview!
diff --git a/core/modules/media/js/plugins/drupalmedia/plugin.js b/core/modules/media/js/plugins/drupalmedia/plugin.js
index 76459293b4..a154b440f6 100644
--- a/core/modules/media/js/plugins/drupalmedia/plugin.js
+++ b/core/modules/media/js/plugins/drupalmedia/plugin.js
@@ -126,12 +126,19 @@
this._tearDownDynamicEditables();
},
data: function data(event) {
+ if (!this.data.hasCaption) {
+ delete this.data.attributes['data-caption'];
+ } else if (this.data.hasCaption && this.oldData && !this.oldData.hasCaption) {
+ this.data.attributes['data-caption'] = ' ';
+ }
+
if (this._previewNeedsServerSideUpdate()) {
editor.fire('lockSnapshot');
this._tearDownDynamicEditables();
this._loadPreview(function (widget) {
widget._setUpDynamicEditables();
+ widget._setUpButtons();
editor.fire('unlockSnapshot');
});
}
@@ -172,7 +179,65 @@
childList: true,
subtree: true
});
+
+ if (captionEditable.$.childNodes.length === 1 && captionEditable.$.childNodes.item(0).nodeName === 'BR') {
+ captionEditable.$.removeChild(captionEditable.$.childNodes.item(0));
+ }
+ }
+ },
+ _setUpButtons: function _setUpButtons() {
+ if (this.element.findOne('.media-embed-error')) {
+ return;
}
+
+ var isElementNode = function isElementNode(n) {
+ return n.type === CKEDITOR.NODE_ELEMENT;
+ };
+
+ var embeddedMediaContainer = this.data.hasCaption ? this.element.findOne('figure') : this.element;
+ var embeddedMedia = embeddedMediaContainer.getFirst(isElementNode);
+
+ if (this.data.link) {
+ embeddedMedia = embeddedMedia.getFirst(isElementNode);
+ }
+
+ embeddedMedia.setStyle('position', 'relative');
+ var editButton = CKEDITOR.dom.element.createFromHtml('' + Drupal.t('Edit media') + '');
+ embeddedMedia.getFirst().insertBeforeMe(editButton);
+
+ var deleteButton = CKEDITOR.dom.element.createFromHtml('' + Drupal.t('Remove media') + '');
+ embeddedMedia.getFirst().insertBeforeMe(deleteButton);
+
+ var widget = this;
+ this.element.findOne('.media-library-item__edit').on('click', function (event) {
+ var saveCallback = function saveCallback(values) {
+ event.cancel();
+ editor.fire('saveSnapshot');
+ if (values.hasOwnProperty('attributes')) {
+ CKEDITOR.tools.extend(values.attributes, widget.data.attributes);
+
+ Object.keys(values.attributes).forEach(function (prop) {
+ if (values.attributes[prop] === false || prop === 'data-align' && values.attributes[prop] === 'none') {
+ delete values.attributes[prop];
+ }
+ });
+ }
+ widget.setData({
+ attributes: values.attributes,
+
+ hasCaption: !!values.hasCaption
+ });
+ editor.fire('saveSnapshot');
+ };
+
+ Drupal.ckeditor.openDialog(editor, Drupal.url('editor/dialog/media/' + editor.config.drupal.format), widget.data, saveCallback, {});
+ });
+ this.element.findOne('.media-library-item__remove').on('click', function (event) {
+ event.cancel();
+ editor.fire('saveSnapshot');
+ widget.repository.del(widget);
+ editor.fire('saveSnapshot');
+ });
},
_tearDownDynamicEditables: function _tearDownDynamicEditables() {
if (this.captionObserver) {
diff --git a/core/modules/media/media.routing.yml b/core/modules/media/media.routing.yml
index 19dadf9e01..6946a205a7 100644
--- a/core/modules/media/media.routing.yml
+++ b/core/modules/media/media.routing.yml
@@ -48,3 +48,11 @@ media.filter.preview:
requirements:
_entity_access: 'filter_format.use'
_custom_access: '\Drupal\media\Controller\MediaFilterController::formatUsesMediaEmbedFilter'
+
+editor.media_dialog:
+ path: '/editor/dialog/media/{editor}'
+ defaults:
+ _form: '\Drupal\media\Form\EditorMediaDialog'
+ _title: 'Edit media'
+ requirements:
+ _entity_access: 'editor.use'
diff --git a/core/modules/media/src/Form/EditorMediaDialog.php b/core/modules/media/src/Form/EditorMediaDialog.php
new file mode 100644
index 0000000000..790f9bdfd8
--- /dev/null
+++ b/core/modules/media/src/Form/EditorMediaDialog.php
@@ -0,0 +1,252 @@
+entityRepository = $entity_repository;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('entity.repository')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFormId() {
+ return 'editor_media_dialog';
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @param \Drupal\editor\Entity\Editor $editor
+ * The text editor to which this dialog corresponds.
+ */
+ public function buildForm(array $form, FormStateInterface $form_state, EditorInterface $editor = NULL) {
+ // This form is special, in that the default values do not come from the
+ // server side, but from the client side, from a text editor. We must cache
+ // this data in form state, because when the form is rebuilt, we will be
+ // receiving values from the form, instead of the values from the text
+ // editor. If we don't cache it, this data will be lost.
+ if (isset($form_state->getUserInput()['editor_object'])) {
+ // By convention, the data that the text editor sends to any dialog is in
+ // the 'editor_object' key.
+ $editor_object = $form_state->getUserInput()['editor_object'];
+ if (empty($editor_object['attributes'])) {
+ throw new MissingDataException("Unable to find the required media embed data.");
+ }
+ $media_embed_element = $editor_object['attributes'];
+ $form_state->set('media_embed_element', $media_embed_element);
+ $form_state->set('hasCaption', !empty($editor_object['hasCaption']));
+ $form_state->setCached(TRUE);
+ }
+ elseif ($form_state->has('media_embed_element')) {
+ // Retrieve the user input from form state.
+ $media_embed_element = $form_state->get('media_embed_element');
+ $has_caption = $form_state->get('hasCaption');
+ }
+ else {
+ throw new BadRequestHttpException("Unable to find the required media embed data.");
+ }
+
+ $form['#tree'] = TRUE;
+ $form['#attached']['library'][] = 'editor/drupal.editor.dialog';
+ $form['#prefix'] = '
';
+ $form['#suffix'] = '
';
+
+ // The alt attribute is *required*, but we allow users to opt-in to empty
+ // alt attributes for the very rare edge cases where that is valid by
+ // specifying two double quotes as the alternative text in the dialog.
+ // However, that *is* stored as an empty alt attribute, so if we're editing
+ // an existing image (which means the src attribute is set) and its alt
+ // attribute is empty, then we show that as two double quotes in the dialog.
+ // @see https://www.drupal.org/node/2307647
+ $media = $this->entityRepository->loadEntityByUuid('media', $media_embed_element['data-entity-uuid']);
+ if ($image_field = $this->getMediaImageSourceField($media)) {
+ $settings = $media->{$image_field}->getItemDefinition()->getSettings();
+ $alt = isset($media_embed_element['alt']) ? $media_embed_element['alt'] : NULL;
+ $title = isset($media_embed_element['title']) ? $media_embed_element['title'] : NULL;
+ if (!empty($settings['alt_field'])) {
+ $form['alt'] = [
+ '#type' => 'textfield',
+ '#title' => $this->t('Alternate text'),
+ '#default_value' => $alt,
+ '#description' => $this->t('Short description of the image used by screen readers and displayed when the image is not loaded. This is important for accessibility.'),
+ '#required_error' => $this->t('Alternative text is required.
(Only in rare cases should this be left empty. To create empty alternative text, enter ""
— two double quotes without any content).'),
+ '#maxlength' => 512,
+ '#placeholder' => $media->{$image_field}->alt,
+ '#parents' => ['attributes', 'alt'],
+ ];
+ }
+ if (!empty($settings['title_field'])) {
+ $form['title'] = [
+ '#type' => 'textfield',
+ '#title' => $this->t('Title'),
+ '#default_value' => $title,
+ '#description' => t('The title is used as a tool tip when the user hovers the mouse over the image.'),
+ // Maxlengths for `title`and `alt` fields follow ImageItem schema.
+ // @See core/modules/image/src/Plugin/Field/FieldType/ImageItem::schema()
+ '#maxlength' => 1024,
+ '#placeholder' => $media->{$image_field}->title,
+ '#parents' => ['attributes', 'title'],
+ ];
+ }
+ }
+
+ // When Drupal core's filter_align is being used, the text editor offers the
+ // ability to change the alignment.
+ if ($editor->getFilterFormat()->filters('filter_align')->status) {
+ $form['align'] = [
+ '#title' => $this->t('Align'),
+ '#type' => 'radios',
+ '#options' => [
+ 'none' => $this->t('None'),
+ 'left' => $this->t('Left'),
+ 'center' => $this->t('Center'),
+ 'right' => $this->t('Right'),
+ ],
+ '#default_value' => (empty($media_embed_element['data-align']) ? 'none' : $media_embed_element['data-align'],
+ '#wrapper_attributes' => ['class' => ['container-inline']],
+ '#attributes' => ['class' => ['container-inline']],
+ '#parents' => ['attributes', 'data-align'],
+ ];
+ }
+
+ // When Drupal core's filter_caption is being used, the text editor offers
+ // the ability to in-place edit the media's caption: show a toggle.
+ if ($editor->getFilterFormat()->filters('filter_caption')->status) {
+ $form['caption'] = [
+ '#title' => $this->t('Caption'),
+ '#type' => 'checkbox',
+ '#default_value' => $has_caption === 'true',
+ '#parents' => ['hasCaption'],
+ ];
+ }
+
+ $form['actions'] = [
+ '#type' => 'actions',
+ ];
+ $form['actions']['save_modal'] = [
+ '#type' => 'submit',
+ '#value' => $this->t('Save'),
+ // No regular submit-handler. This form only works via JavaScript.
+ '#submit' => [],
+ '#ajax' => [
+ 'callback' => '::submitForm',
+ 'event' => 'click',
+ ],
+ ];
+
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state) {
+ $response = new AjaxResponse();
+
+ // When the `alt` attribute is set to two double quotes, transform it to the
+ // empty string: two double quotes signify "empty alt attribute". See above.
+ if (trim($form_state->getValue(['attributes', 'alt'], '')) === '""') {
+ $form_state->setValue(['attributes', 'alt'], '""');
+ }
+
+ // The `alt` and `title` attributes are optional: if they're not set, their
+ // default values simply will not be overridden.
+ if ($form_state->hasValue(['attributes', 'alt']) && trim($form_state->getValue(['attributes', 'alt'])) === '') {
+ $form_state->unsetValue(['attributes', 'alt']);
+ }
+ if ($form_state->hasValue(['attributes', 'title']) && trim($form_state->getValue(['attributes', 'title'])) === '') {
+ $form_state->unsetValue(['attributes', 'title']);
+ }
+
+ // If `data-align`is set to "none", remove the attribute.
+ if ($form_state->hasValue(['attributes', 'data-align']) && $form_state->getValue(['attributes', 'data-align']) === 'none') {
+ $form_state->unsetValue(['attributes', 'data-align']);
+ }
+
+ if ($form_state->getErrors()) {
+ unset($form['#prefix'], $form['#suffix']);
+ $form['status_messages'] = [
+ '#type' => 'status_messages',
+ '#weight' => -10,
+ ];
+ $response->addCommand(new HtmlCommand('#editor-media-dialog-form', $form));
+ }
+ else {
+ // Only send back the relevant values.
+ $values = [
+ 'hasCaption' => $form_state->getValue('hasCaption'),
+ 'attributes' => $form_state->getValue('attributes'),
+ ];
+ $response->addCommand(new EditorDialogSave($values));
+ $response->addCommand(new CloseModalDialogCommand());
+ }
+
+ return $response;
+ }
+
+ /**
+ * Get image field from source config.
+ *
+ * @param \Drupal\media\MediaInterface $media
+ * Embedded media.
+ *
+ * @return string|null
+ * String of image field name.
+ */
+ protected function getMediaImageSourceField(MediaInterface $media) {
+ $field_definition = $media->getSource()
+ ->getSourceFieldDefinition($media->bundle->entity);
+ $item_class = $field_definition->getItemDefinition()->getClass();
+ if (is_a($item_class, ImageItem::class)) {
+ return $field_definition->getName();
+ }
+ return NULL;
+ }
+
+}
diff --git a/core/modules/media/src/Plugin/CKEditorPlugin/DrupalMedia.php b/core/modules/media/src/Plugin/CKEditorPlugin/DrupalMedia.php
index 35717d5a76..b01a9d23e4 100644
--- a/core/modules/media/src/Plugin/CKEditorPlugin/DrupalMedia.php
+++ b/core/modules/media/src/Plugin/CKEditorPlugin/DrupalMedia.php
@@ -124,6 +124,7 @@ public function getCssFiles(Editor $editor) {
$this->moduleExtensionList->getPath('media') . '/css/filter.media_embed.css',
$this->moduleExtensionList->getPath('media') . '/css/plugins/drupalmedia/ckeditor.drupalmedia.css',
$this->moduleExtensionList->getPath('system') . '/css/components/hidden.module.css',
+ $this->moduleExtensionList->getPath('media_library') . '/css/media_library.theme.css',
];
}
diff --git a/core/modules/media/src/Plugin/Filter/MediaEmbed.php b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
index 3145258cba..502a0af142 100644
--- a/core/modules/media/src/Plugin/Filter/MediaEmbed.php
+++ b/core/modules/media/src/Plugin/Filter/MediaEmbed.php
@@ -414,6 +414,13 @@ protected function applyPerEmbedMediaOverrides(\DOMElement $node, MediaInterface
$settings = $media->{$image_field}->getItemDefinition()->getSettings();
if (!empty($settings['alt_field']) && $node->hasAttribute('alt')) {
+ // Allow the display of the image without an alt tag in special cases.
+ // Since setting the value in the EditorMediaDialog to an empty string
+ // restores the default value, this allows special cases where the
+ // alt should not be set to the default value but should be empty.
+ if ($node->getAttribute('alt') === '""') {
+ $node->setAttribute('alt', NULL);
+ }
$media->{$image_field}->alt = $node->getAttribute('alt');
// All media entities have a thumbnail. In the case of image media, it
// is conceivable that a particular view mode chooses to display the
@@ -428,6 +435,9 @@ protected function applyPerEmbedMediaOverrides(\DOMElement $node, MediaInterface
}
if (!empty($settings['title_field']) && $node->hasAttribute('title')) {
+ if ($node->getAttribute('title') === '""') {
+ $node->setAttribute('title', NULL);
+ }
// See above, the explanations for `alt` also apply to `title`.
$media->{$image_field}->title = $node->getAttribute('title');
$media->thumbnail->title = $node->getAttribute('title');
diff --git a/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php b/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
index d0e7588697..911654099e 100644
--- a/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
+++ b/core/modules/media/tests/src/FunctionalJavascript/CKEditorIntegrationTest.php
@@ -3,6 +3,7 @@
namespace Drupal\Tests\media\FunctionalJavascript;
use Drupal\Component\Utility\Html;
+use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\Url;
use Drupal\editor\Entity\Editor;
use Drupal\file\Entity\File;
@@ -86,6 +87,8 @@ protected function setUp() {
'DrupalLink',
'DrupalUnlink',
'DrupalImage',
+ 'Undo',
+ 'Redo',
],
],
],
@@ -139,6 +142,8 @@ protected function setUp() {
* @see \Drupal\Tests\media\Kernel\MediaEmbedFilterTest::testOnlyDrupalMediaTagProcessed()
*/
public function testOnlyDrupalMediaTagProcessed() {
+ $session = $this->getSession();
+ $page = $session->getPage();
$original_value = $this->host->body->value;
$this->host->body->value = str_replace('drupal-media', 'p', $original_value);
$this->host->save();
@@ -147,7 +152,7 @@ public function testOnlyDrupalMediaTagProcessed() {
$this->drupalGet($this->host->toUrl('edit-form'));
$this->waitForEditor();
$this->assignNameToCkeditorIframe();
- $this->getSession()->switchToIFrame('ckeditor');
+ $session->switchToIFrame('ckeditor');
$assert_session = $this->assertSession();
$this->assertEmpty($assert_session->waitForElementVisible('css', 'img[src*="image-test.png"]', 1000));
$assert_session->elementNotExists('css', 'figure');
@@ -156,10 +161,10 @@ public function testOnlyDrupalMediaTagProcessed() {
$this->host->save();
// Assert that `` is upcast into a CKEditor Widget.
- $this->getSession()->reload();
+ $session->reload();
$this->waitForEditor();
$this->assignNameToCkeditorIframe();
- $this->getSession()->switchToIFrame('ckeditor');
+ $session->switchToIFrame('ckeditor');
$this->assertNotEmpty($assert_session->waitForElementVisible('css', 'img[src*="image-test.png"]'));
$assert_session->elementExists('css', 'figure');
}
@@ -168,6 +173,7 @@ public function testOnlyDrupalMediaTagProcessed() {
* Tests that failed media embed preview requests inform the end user.
*/
public function testPreviewFailure() {
+ $session = $this->getSession();
// Assert that a request to the `media.filter.preview` route that does not
// result in a 200 response (due to server error or network error) is
// handled in the JavaScript by displaying the expected error message.
@@ -175,7 +181,7 @@ public function testPreviewFailure() {
$this->drupalGet($this->host->toUrl('edit-form'));
$this->waitForEditor();
$this->assignNameToCkeditorIframe();
- $this->getSession()->switchToIFrame('ckeditor');
+ $session->switchToIFrame('ckeditor');
$assert_session = $this->assertSession();
$this->assertEmpty($assert_session->waitForElementVisible('css', 'img[src*="image-test.png"]', 1000));
$assert_session->elementNotExists('css', 'figure');
@@ -185,10 +191,10 @@ public function testPreviewFailure() {
// Now assert that the error doesn't appear when the override to force an
// error is removed.
$this->container->get('state')->set('test_media_filter_controller_throw_error', FALSE);
- $this->getSession()->reload();
+ $session->reload();
$this->waitForEditor();
$this->assignNameToCkeditorIframe();
- $this->getSession()->switchToIFrame('ckeditor');
+ $session->switchToIFrame('ckeditor');
$this->assertNotEmpty($assert_session->waitForElementVisible('css', 'img[src*="image-test.png"]'));
}
@@ -196,6 +202,7 @@ public function testPreviewFailure() {
* The CKEditor Widget must load a preview generated using the default theme.
*/
public function testPreviewUsesDefaultThemeAndIsClientCacheable() {
+ $session = $this->getSession();
// Make the node edit form use the admin theme, like on most Drupal sites.
$this->config('node.settings')
->set('use_admin_theme', TRUE)
@@ -218,7 +225,7 @@ public function testPreviewUsesDefaultThemeAndIsClientCacheable() {
$this->drupalGet($this->host->toUrl('edit-form'));
$this->waitForEditor();
$this->assignNameToCkeditorIframe();
- $this->getSession()->switchToIFrame('ckeditor');
+ $session->switchToIFrame('ckeditor');
$assert_session = $this->assertSession();
$this->assertNotEmpty($assert_session->waitForElementVisible('css', 'img[src*="image-test.png"]'));
$element = $assert_session->elementExists('css', '[data-media-embed-test-active-theme]');
@@ -235,7 +242,7 @@ public function testPreviewUsesDefaultThemeAndIsClientCacheable() {
$this->assertNotEmpty($assert_session->waitForElement('css', 'textarea.cke_source'));
$this->pressEditorButton('source');
$this->assignNameToCkeditorIframe();
- $this->getSession()->switchToIFrame('ckeditor');
+ $session->switchToIFrame('ckeditor');
$this->assertNotEmpty($assert_session->waitForElementVisible('css', 'img[src*="image-test.png"]'));
$this->assertSame(0, $this->getLastPreviewRequestTransferSize());
}
@@ -244,23 +251,52 @@ public function testPreviewUsesDefaultThemeAndIsClientCacheable() {
* Tests caption editing in the CKEditor widget.
*/
public function testEditableCaption() {
+ $session = $this->getSession();
+ $page = $session->getPage();
+ $assert_session = $this->assertSession();
$this->drupalGet($this->host->toUrl('edit-form'));
$this->waitForEditor();
$this->assignNameToCkeditorIframe();
-
+ $session->switchToIFrame('ckeditor');
+ // Assert that figcaption element exists within the drupal-media element.
+ $figcaption = $assert_session->waitForElement('css', 'figcaption');
+ $this->assertNotEmpty($figcaption);
+ $this->assertSame('baz', $figcaption->getHtml());
+ // Test that disabling the caption in the metadata dialog removes it
+ // from the drupal-media element.
+ $page->clickLink('Edit media');
+ $this->waitforMetadataDialog();
+ $page->uncheckField('hasCaption');
+ $this->submitDialog();
+ $session->switchToIFrame('ckeditor');
+ $drupal_media = $assert_session->waitForElementVisible('css', 'drupal-media', 2000);
+ $this->assertNotEmpty($drupal_media);
+ // Wait for element to update without figcaption.
+ $result = $page->waitFor(10, function () use ($drupal_media) {
+ return empty($drupal_media->find('css', 'figcaption'));
+ });
+ // Will be true if no figcaption exists.
+ $this->assertTrue($result);
+ // Test that enabling the caption in the metadata dialog adds an editable
+ // caption to the embedded media.
+ $page->clickLink('Edit media');
+ $this->waitforMetadataDialog();
+ $page->checkField('hasCaption');
+ $this->submitDialog();
+ $session->switchToIFrame('ckeditor');
+ $drupal_media = $assert_session->waitForElementVisible('css', 'drupal-media figcaption', 2000);
+ $this->assertNotEmpty($drupal_media);
// Type in the widget's editable for the caption.
- $this->getSession()->switchToIFrame('ckeditor');
- $assert_session = $this->assertSession();
$this->assertNotEmpty($assert_session->waitForElement('css', 'figcaption'));
$this->setCaption('Caught in a landslide! No escape from reality!');
- $this->getSession()->switchToIFrame('ckeditor');
+ $session->switchToIFrame('ckeditor');
$assert_session->elementExists('css', 'figcaption > em');
$assert_session->elementExists('css', 'figcaption > strong')->click();
// Select the element and unbold it.
$this->clickPathLinkByTitleAttribute("strong element");
$this->pressEditorButton('bold');
- $this->getSession()->switchToIFrame('ckeditor');
+ $session->switchToIFrame('ckeditor');
$assert_session->elementExists('css', 'figcaption > em');
$assert_session->elementNotExists('css', 'figcaption > strong');
@@ -286,7 +322,7 @@ public function testEditableCaption() {
$source->setValue(Html::serialize($dom));
$this->pressEditorButton('source');
$this->assignNameToCkeditorIframe();
- $this->getSession()->switchToIFrame('ckeditor');
+ $session->switchToIFrame('ckeditor');
$figcaption = $assert_session->waitForElement('css', 'figcaption');
$this->assertNotEmpty($figcaption);
$this->assertSame($poor_boy_text, $figcaption->getHtml());
@@ -308,9 +344,9 @@ public function testEditableCaption() {
// Wait for the live preview in the CKEditor widget to finish loading, then
// edit the link; no `data-cke-saved-href` attribute should exist on it.
- $this->getSession()->switchToIFrame('ckeditor');
+ $session->switchToIFrame('ckeditor');
$figcaption = $assert_session->waitForElement('css', 'figcaption');
- $page = $this->getSession()->getPage();
+
// Wait for AJAX refresh.
$page->waitFor(10, function () use ($figcaption) {
return $figcaption->find('xpath', '//a[@href="https://www.drupal.org"]');
@@ -322,9 +358,8 @@ public function testEditableCaption() {
$this->assertNotEmpty($field);
$field->setValue('https://www.drupal.org/project/drupal');
$assert_session->elementExists('css', 'button.form-submit')->press();
- $this->getSession()->switchToIFrame('ckeditor');
+ $session->switchToIFrame('ckeditor');
$figcaption = $assert_session->waitForElement('css', 'figcaption');
- $page = $this->getSession()->getPage();
// Wait for AJAX refresh.
$page->waitFor(10, function () use ($figcaption) {
return $figcaption->find('xpath', '//a[@href="https://www.drupal.org/project/drupal"]');
@@ -347,27 +382,184 @@ public function testEditableCaption() {
$this->drupalGet($this->host->toUrl('edit-form'));
$this->waitForEditor();
$this->assignNameToCkeditorIframe();
- $this->getSession()->switchToIFrame('ckeditor');
+ $session->switchToIFrame('ckeditor');
$this->assertNotEmpty($assert_session->waitForElementVisible('css', 'figcaption'));
$this->setCaption('Scaramouch, Scaramouch, will you do the Fandango?');
// Erase the caption in the CKEditor Widget, verify the still
// exists and contains placeholder text, then type something else.
$this->setCaption('');
- $this->getSession()->switchToIFrame('ckeditor');
+ $session->switchToIFrame('ckeditor');
$assert_session->elementContains('css', 'figcaption', '');
$assert_session->elementAttributeContains('css', 'figcaption', 'data-placeholder', 'Enter caption here');
$this->setCaption('Fin.');
- $this->getSession()->switchToIFrame('ckeditor');
+ $session->switchToIFrame('ckeditor');
$assert_session->elementContains('css', 'figcaption', 'Fin.');
}
+ /**
+ * Tests the EditorMediaDialog can set alt and title attributes.
+ */
+ public function testAltAndTitle() {
+ // Update the default media view mode to use image formatter,
+ // to test title field override.
+ EntityViewDisplay::create([
+ 'targetEntityType' => 'media',
+ 'bundle' => 'image',
+ 'mode' => 'default',
+ 'status' => TRUE,
+ ])->setComponent('field_media_image', [
+ 'type' => 'image',
+ 'weight' => 2,
+ 'region' => 'content',
+ 'label' => 'hidden',
+ 'settings' => [
+ 'image_style' => 'medium',
+ 'image_link' => '',
+ ],
+ 'third_party_settings' => [],
+ ])->removeComponent('thumbnail')
+ ->save();
+ $session = $this->getSession();
+ $page = $session->getPage();
+ $assert_session = $this->assertSession();
+ $this->drupalGet($this->host->toUrl('edit-form'));
+ $this->waitForEditor();
+ $this->assignNameToCkeditorIframe();
+ $session->switchToIFrame('ckeditor');
+
+ $config_combinations = [
+ [
+ 'settings' => [
+ 'settings.alt_field' => FALSE,
+ 'settings.title_field' => FALSE,
+ ],
+ 'expected_fields' => [
+ 'attributes[alt]' => FALSE,
+ 'attributes[title]' => FALSE,
+ ],
+ ],
+ [
+ 'settings' => [
+ 'settings.alt_field' => TRUE,
+ 'settings.title_field' => FALSE,
+ ],
+ 'expected_fields' => [
+ 'attributes[alt]' => TRUE,
+ 'attributes[title]' => FALSE,
+ ],
+ ],
+ [
+ 'settings' => [
+ 'settings.alt_field' => FALSE,
+ 'settings.title_field' => TRUE,
+ ],
+ 'expected_fields' => [
+ 'attributes[alt]' => FALSE,
+ 'attributes[title]' => TRUE,
+ ],
+ ],
+ [
+ 'settings' => [
+ 'settings.alt_field' => TRUE,
+ 'settings.title_field' => TRUE,
+ ],
+ 'expected_fields' => [
+ 'attributes[alt]' => TRUE,
+ 'attributes[title]' => TRUE,
+ ],
+ ],
+ ];
+
+ foreach ($config_combinations as $data) {
+ $this->config('field.field.media.image.field_media_image')
+ ->set('settings.alt_field', $data['settings']['settings.alt_field'])
+ ->set('settings.title_field', $data['settings']['settings.title_field'])
+ ->save();
+ $this->container->get('cache.discovery')->deleteAll();
+ // Wait for preview.
+ $this->assertNotEmpty($assert_session->waitForElementVisible('css', 'drupal-media', 2000));
+ $page->clickLink('Edit media');
+ $this->waitForMetadataDialog();
+ $alt = !empty($page->findField('attributes[alt]'));
+ $this->assertSame($data['expected_fields']['attributes[alt]'], $alt);
+ $title = !empty($page->findField('attributes[title]'));
+ $this->assertSame($data['expected_fields']['attributes[title]'], $title);
+ $page->pressButton('Close');
+ $session->switchToIFrame('ckeditor');
+ }
+
+ $img = $assert_session->waitForElementVisible('xpath', '//img[contains(@alt, "default alt")]');
+ $this->assertEquals('default title', $img->getAttribute('title'));
+ $page->clickLink('Edit media');
+ $this->waitForMetadataDialog();
+ $alt = $page->findField('attributes[alt]');
+ $title = $page->findField('attributes[title]');
+ // Assert that the placeholder is set to the value of the media field's alt text.
+ $this->assertSame('default alt', $alt->getAttribute('placeholder'));
+ $this->assertSame('default title', $title->getAttribute('placeholder'));
+ $who_is_zartan = 'Zartan is the leader of the Dreadnoks.';
+ $decepticons = 'Robotic lifeforms from the planet Cybertron';
+ $page->fillField('attributes[alt]', $who_is_zartan);
+ $page->fillField('attributes[title]', $decepticons);
+ $this->submitDialog();
+ $session->switchToIFrame('ckeditor');
+ $img = $assert_session->waitForElementVisible('xpath', '//img[contains(@alt, "' . $who_is_zartan . '")]');
+ $this->assertNotEmpty($img);
+ $this->assertSame($decepticons, $img->getAttribute('title'));
+ $page->clickLink('Edit media');
+ $this->waitForMetadataDialog();
+ $alt = $page->findField('attributes[alt]');
+ $title = $page->findField('attributes[title]');
+ $this->assertSame($who_is_zartan, $alt->getValue());
+ $this->assertSame($decepticons, $title->getValue());
+ $cobra_commander_bio = 'The supreme leader of the terrorist organization Cobra';
+ $alt->setValue($cobra_commander_bio);
+ $who_is_megatron = 'the evil Decepticon leader';
+ $title->setValue($who_is_megatron);
+ $this->submitDialog();
+ $session->switchToIFrame('ckeditor');
+ $img = $assert_session->waitForElementVisible('xpath', '//img[contains(@alt, "' . $cobra_commander_bio . '")]');
+ $this->assertNotEmpty($img);
+ $this->assertSame($who_is_megatron, $img->getAttribute('title'));
+ $page->clickLink('Edit media');
+ $this->waitForMetadataDialog();
+ $alt = $page->findField('attributes[alt]');
+ $title = $page->findField('attributes[title]');
+ $this->assertSame($cobra_commander_bio, $alt->getValue());
+ $this->assertSame($who_is_megatron, $title->getValue());
+ // Test that setting value to double quote will allow setting the alt
+ // and title to empty.
+ $alt->setValue('""');
+ $title->setValue('""');
+ $this->submitDialog();
+ $session->switchToIFrame('ckeditor');
+ $img = $assert_session->waitForElementVisible('xpath', '//img');
+ // Wait for element to update.
+ $result = $page->waitFor(10, function () use ($img, $cobra_commander_bio) {
+ return ($img->getAttribute('alt') !== $cobra_commander_bio);
+ });
+ $this->assertTrue($result);
+ $this->assertEmpty($img->getAttribute('alt'));
+ $this->assertEmpty($img->getAttribute('title'));
+ $page->clickLink('Edit media');
+ $this->waitForMetadataDialog();
+ // Test that setting value to back to empty string restores the default values.
+ $page->fillField('attributes[alt]', "");
+ $page->fillField('attributes[title]', "");
+ $this->submitDialog();
+ $session->switchToIFrame('ckeditor');
+ $img = $assert_session->waitForElementVisible('xpath', '//img[contains(@alt, "default alt")]');
+ $this->assertEquals('default title', $img->getAttribute('title'));
+ }
+
/**
* Tests linkability of the CKEditor widget.
*
* @dataProvider linkabilityProvider
*/
public function testLinkability($drupalimage_is_enabled) {
+ $session = $this->getSession();
if (!$drupalimage_is_enabled) {
// Remove the `drupalimage` plugin's `DrupalImage` button.
$editor = Editor::load('test_format');
@@ -377,9 +569,11 @@ public function testLinkability($drupalimage_is_enabled) {
foreach ($row as $group_key => $group) {
foreach ($group['items'] as $item_key => $item) {
if ($item === 'DrupalImage') {
- unset($settings['toolbar']['rows'][$row_key][$group_key]['items'][$item_key]);
+ unset($group['items'][$item_key]);
+ break;
}
}
+ $settings['toolbar']['rows'][$row_key][$group_key]['items'] = array_values($group['items']);
}
}
$editor->setSettings($settings);
@@ -398,7 +592,7 @@ public function testLinkability($drupalimage_is_enabled) {
$this->drupalGet($this->host->toUrl('edit-form'));
$this->waitForEditor();
$this->assignNameToCkeditorIframe();
- $this->getSession()->switchToIFrame('ckeditor');
+ $session->switchToIFrame('ckeditor');
$assert_session = $this->assertSession();
// Select the CKEditor Widget.
@@ -410,7 +604,7 @@ public function testLinkability($drupalimage_is_enabled) {
// contain link-related context menu items.
$this->openContextMenu();
$this->assignNameToCkeditorPanelIframe();
- $this->getSession()->switchToIFrame('panel');
+ $session->switchToIFrame('panel');
$this->assertContextMenuItemNotExists('Edit Link');
$this->assertContextMenuItemNotExists('Unlink');
$this->closeContextMenu();
@@ -424,7 +618,7 @@ public function testLinkability($drupalimage_is_enabled) {
$this->assertNotEmpty($field);
$field->setValue('https://www.drupal.org');
$assert_session->elementExists('css', 'button.form-submit')->press();
- $this->getSession()->switchToIFrame('ckeditor');
+ $session->switchToIFrame('ckeditor');
$link = $assert_session->waitForElementVisible('css', 'a[href="https://www.drupal.org"]');
$this->assertNotEmpty($link);
@@ -434,13 +628,13 @@ public function testLinkability($drupalimage_is_enabled) {
$this->assertNotEmpty($drupalmedia);
$drupalmedia->click();
$this->openContextMenu();
- $this->getSession()->switchToIFrame('panel');
+ $session->switchToIFrame('panel');
$this->assertContextMenuItemExists('Edit Link');
$this->assertContextMenuItemExists('Unlink');
$this->closeContextMenu();
// Save the entity.
- $this->getSession()->switchToIFrame();
+ $session->switchToIFrame();
$assert_session->buttonExists('Save')->press();
// Verify the saved entity when viewed also contains the linked media.
@@ -457,7 +651,7 @@ public function testLinkability($drupalimage_is_enabled) {
$field->setValue('https://wikipedia.org');
$assert_session->elementExists('css', 'button.form-submit')->press();
$this->assignNameToCkeditorIframe();
- $this->getSession()->switchToIFrame('ckeditor');
+ $session->switchToIFrame('ckeditor');
$link = $assert_session->waitForElementVisible('css', 'body > a[href="https://wikipedia.org"]');
$this->assertNotEmpty($link);
$assert_session->elementExists('css', 'body > .cke_widget_drupalmedia > drupal-media > figure > a[href="https://www.drupal.org"]');
@@ -469,30 +663,30 @@ public function testLinkability($drupalimage_is_enabled) {
$this->assertNotEmpty($drupalmedia);
$drupalmedia->click();
$this->openContextMenu();
- $this->getSession()->switchToIFrame();
+ $session->switchToIFrame();
$this->assertEditorButtonEnabled('drupalunlink');
$this->assignNameToCkeditorPanelIframe();
- $this->getSession()->switchToIFrame('panel');
+ $session->switchToIFrame('panel');
$this->assertContextMenuItemExists('Edit Link');
$this->assertContextMenuItemExists('Unlink');
// Test that moving focus to another element causes the `drupalunlink`
// button to become disabled and causes link-related context menu items to
// disappear.
- $this->getSession()->switchToIFrame();
- $this->getSession()->switchToIFrame('ckeditor');
+ $session->switchToIFrame();
+ $session->switchToIFrame('ckeditor');
$p = $assert_session->waitForElementVisible('xpath', "//p[contains(text(), 'The pirate is irate')]");
$this->assertNotEmpty($p);
$p->click();
$this->assertEditorButtonDisabled('drupalunlink');
- $this->getSession()->switchToIFrame('panel');
+ $session->switchToIFrame('panel');
$this->assertContextMenuItemExists('Edit Link');
$this->assertContextMenuItemExists('Unlink');
// To switch from the context menu iframe ("panel") back to the CKEditor
// iframe, we first have to reset to top frame.
- $this->getSession()->switchToIFrame();
- $this->getSession()->switchToIFrame('ckeditor');
+ $session->switchToIFrame();
+ $session->switchToIFrame('ckeditor');
// Test that moving focus to the `drupalimage` CKEditor Widget enables the
// `drupalunlink` button again, because it is a linked image.
@@ -501,22 +695,22 @@ public function testLinkability($drupalimage_is_enabled) {
$this->assertNotEmpty($drupalimage);
$drupalimage->click();
$this->assertEditorButtonEnabled('drupalunlink');
- $this->getSession()->switchToIFrame('panel');
+ $session->switchToIFrame('panel');
$this->assertContextMenuItemExists('Edit Link');
$this->assertContextMenuItemExists('Unlink');
- $this->getSession()->switchToIFrame();
- $this->getSession()->switchToIFrame('ckeditor');
+ $session->switchToIFrame();
+ $session->switchToIFrame('ckeditor');
}
// Tests the `drupalunlink` button for the `drupalmedia` CKEditor Widget.
$drupalmedia->click();
$this->assertEditorButtonEnabled('drupalunlink');
- $this->getSession()->switchToIFrame('panel');
+ $session->switchToIFrame('panel');
$this->assertContextMenuItemExists('Edit Link');
$this->assertContextMenuItemExists('Unlink');
$this->pressEditorButton('drupalunlink');
$this->assertEditorButtonDisabled('drupalunlink');
- $this->getSession()->switchToIFrame('ckeditor');
+ $session->switchToIFrame('ckeditor');
$assert_session->elementNotExists('css', 'figure > a[href="https://www.drupal.org"] > .media--type-image > .field--type-image > img[src*="image-test.png"]');
$assert_session->elementExists('css', 'figure .media--type-image > .field--type-image > img[src*="image-test.png"]');
if ($drupalimage_is_enabled) {
@@ -525,7 +719,7 @@ public function testLinkability($drupalimage_is_enabled) {
$this->assertEditorButtonEnabled('drupalunlink');
$this->pressEditorButton('drupalunlink');
$this->assertEditorButtonDisabled('drupalunlink');
- $this->getSession()->switchToIFrame('ckeditor');
+ $session->switchToIFrame('ckeditor');
$assert_session->elementNotExists('css', 'p > a[href="https://www.drupal.org/association"] > img[src*="image-test.png"]');
$assert_session->elementExists('css', 'p > img[src*="image-test.png"]');
}
@@ -611,28 +805,163 @@ public function previewAccessProvider() {
}
/**
- * Tests that alignment is reflected onto the CKEditor Widget wrapper.
+ * Tests alignment integration.
+ *
+ * Tests that alignment is reflected onto the CKEditor Widget wrapper, that
+ * the EditorMediaDialog allows altering the alignment and that the changes
+ * are reflected on the widget and downcast drupal-media tag.
*/
- public function testAlignmentClasses() {
+ public function testAlignmentIntegration() {
+ $session = $this->getSession();
+ $page = $session->getPage();
+ $assert_session = $this->assertSession();
+ $this->drupalGet($this->host->toUrl('edit-form'));
+ $this->waitForEditor();
+ $this->assignNameToCkeditorIframe();
+ $session->switchToIFrame('ckeditor');
$alignments = [
'right',
'left',
'center',
];
- $assert_session = $this->assertSession();
foreach ($alignments as $alignment) {
- $this->host->body->value = '';
- $this->host->save();
-
- // The upcasted CKEditor Widget's wrapper must get an `align-*` class.
- $this->drupalGet($this->host->toUrl('edit-form'));
- $this->waitForEditor();
+ $this->assertNotEmpty($assert_session->waitForElementVisible('css', 'drupal-media', 2000));
+ $page->clickLink('Edit media');
+ $this->waitforMetadataDialog();
+ $page->fillField('attributes[data-align]', $alignment);
+ $this->submitDialog();
+ $session->switchToIFrame('ckeditor');
+ // Assert that data-align property is converted to either align-right',
+ // `align-left` or `align-center' on widget, caption figure and
+ // drupal-media tag.
+ $this->assertNotEmpty($assert_session->waitForElementVisible('xpath', '//drupal-media[@data-align="' . $alignment . '"]', 2000));
+ $this->assertNotEmpty($assert_session->waitForElementVisible('css', '.caption-drupal-media.align-' . $alignment, 2000));
+ $widget = $assert_session->elementExists('css', '.cke_widget_drupalmedia.align-' . $alignment);
+ $this->assertNotEmpty($widget);
+
+ // Assert that the resultant downcast drupal-media tag has the proper
+ // `data-align` attribute.
+ $this->pressEditorButton('source');
+ $source = $assert_session->elementExists('css', 'textarea.cke_source');
+ $value = $source->getValue();
+ $dom = Html::load($value);
+ $xpath = new \DOMXPath($dom);
+ $drupal_media = $xpath->query('//drupal-media')[0];
+ $this->assertSame($alignment, $drupal_media->getAttribute('data-align'));
+ $this->pressEditorButton('source');
$this->assignNameToCkeditorIframe();
- $this->getSession()->switchToIFrame('ckeditor');
- $wrapper = $assert_session->waitForElementVisible('css', '.cke_widget_drupalmedia', 2000);
- $this->assertNotEmpty($wrapper);
- $this->assertTrue($wrapper->hasClass('align-' . $alignment));
+ $session->switchToIFrame('ckeditor');
+ }
+ // Test that setting alignment to none removes the attribute.
+ $this->assertNotEmpty($assert_session->waitForElementVisible('css', 'drupal-media', 2000));
+ $page->clickLink('Edit media');
+ $this->waitforMetadataDialog();
+ $page->fillField('attributes[data-align]', 'none');
+ $this->submitDialog();
+ $session->switchToIFrame('ckeditor');
+ $drupal_media = $assert_session->waitForElementVisible('xpath', '//drupal-media', 2000);
+ $this->assertNotEmpty($drupal_media);
+ if ($drupal_media->hasAttribute('data-align')) {
+ // Wait for element to update.
+ $page->waitFor(10, function () use ($drupal_media) {
+ return empty($drupal_media->hasAttribute('data-align'));
+ });
+ }
+ $this->assertFalse($drupal_media->hasAttribute('data-align'));
+ // Assert that neither the widget nor the caption figure have alignment classes.
+ $figure = $assert_session->waitForElementVisible('css', '.caption-drupal-media', 2000);
+ $this->assertNotEmpty($figure);
+ $widget = $assert_session->elementExists('css', '.cke_widget_drupalmedia');
+ $this->assertNotEmpty($widget);
+ foreach ($alignments as $alignment) {
+ $this->assertFalse($figure->hasClass('align-' . $alignment));
+ $this->assertFalse($widget->hasClass('align-' . $alignment));
}
+ // Assert that the resultant downcast drupal-media tag has no data-align
+ // attribute.
+ $this->pressEditorButton('source');
+ $source = $assert_session->elementExists('css', 'textarea.cke_source');
+ $value = $source->getValue();
+ $dom = Html::load($value);
+ $xpath = new \DOMXPath($dom);
+ $drupal_media = $xpath->query('//drupal-media')[0];
+ $this->assertEmpty($drupal_media->getAttribute('data-align'));
+ }
+
+ /**
+ * Tests the delete button within the drupalmedia widget.
+ */
+ public function testDeleteButton() {
+ $session = $this->getSession();
+ $page = $session->getPage();
+ $assert_session = $this->assertSession();
+ $this->drupalGet($this->host->toUrl('edit-form'));
+ $this->waitForEditor();
+ $this->assignNameToCkeditorIframe();
+ $session->switchToIFrame('ckeditor');
+ $drupal_media = $assert_session->waitForElementVisible('css', 'drupal-media', 2000);
+ $this->assertNotEmpty($drupal_media);
+ $assert_session->linkExists('Edit media');
+ // Test that the delete button removes the media.
+ $page->clickLink('Delete media');
+ $result = $page->waitFor(10, function () use ($page) {
+ return empty($page->find('css', 'drupal-media'));
+ });
+ $this->assertTrue($result);
+ // Test that undo button restores the deleted media embed.
+ $this->pressEditorButton('undo');
+ $session->switchToIFrame('ckeditor');
+ $drupal_media = $assert_session->waitForElementVisible('css', 'drupal-media', 2000);
+ $this->assertNotEmpty($drupal_media);
+ // Test that the redo button removes the media again.
+ $this->pressEditorButton('redo');
+ $result = $page->waitFor(10, function () use ($page) {
+ return empty($page->find('css', 'drupal-media'));
+ });
+ $this->assertTrue($result);
+ }
+
+ /**
+ * Waits for the form that allows editing metadata.
+ *
+ * @see /core/modules/media/src/Form/EditorMediaDialog.php
+ */
+ protected function waitForMetadataDialog() {
+ $page = $this->getSession()->getPage();
+ $this->getSession()->switchToIFrame();
+ // Wait for the dialog to open.
+ $result = $page->waitFor(10, function () use ($page) {
+ $metadata_editor = $page->find('css', 'form.editor-media-dialog');
+ return !empty($metadata_editor);
+ });
+ $this->assertTrue($result);
+ }
+
+ /**
+ * Closes and submits the metadata dialog.
+ *
+ * @throws \Behat\Mink\Exception\ElementNotFoundException
+ */
+ protected function submitDialog() {
+ $dialog_buttons = $this->assertSession()
+ ->elementExists('css', 'div.ui-dialog-buttonpane');
+ $this->assertNotEmpty($dialog_buttons);
+ $dialog_buttons->pressButton('Save');
+ }
+
+ /**
+ * Closes the metadata dialog.
+ *
+ * @throws \Behat\Mink\Exception\ElementNotFoundException
+ */
+ protected function closeDialog() {
+ $page = $this->getSession()->getPage();
+ $page->pressButton('Close');
+ $result = $page->waitFor(10, function () use ($page) {
+ $metadata_editor = $page->find('css', 'form.editor-media-dialog');
+ return empty($metadata_editor);
+ });
+ $this->assertTrue($result);
}
/**