first commit

This commit is contained in:
2024-07-15 12:33:27 +02:00
commit ce50ae282b
22084 changed files with 2623791 additions and 0 deletions

View File

@@ -0,0 +1,15 @@
name: 'Media Library Form Overwrite test'
type: module
description: 'Test module for Media Library.'
package: Testing
dependencies:
- drupal:image
- drupal:media_library
- drupal:menu_ui
- drupal:node
- drupal:path
# Information added by Drupal.org packaging script on 2024-07-04
version: '10.3.1'
project: 'drupal'
datestamp: 1720094222

View File

@@ -0,0 +1,12 @@
<?php
/**
* @file
* Contains.
*/
use Drupal\media_library_form_overwrite_test\Form\TestAddForm;
function media_library_form_overwrite_test_media_source_info_alter(array &$sources) {
$sources['image']['forms']['media_library_add'] = TestAddForm::class;
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Drupal\media_library_form_overwrite_test\Form;
use Drupal\Core\Form\FormStateInterface;
use Drupal\media_library\Form\AddFormBase;
/**
* Test add form.
*/
class TestAddForm extends AddFormBase {
/**
* {@inheritdoc}
*/
protected function buildInputElement(array $form, FormStateInterface $form_state) {
return [];
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'test_add_form';
}
}

View File

@@ -0,0 +1,16 @@
name: 'Media Library test'
type: module
description: 'Test module for Media Library.'
package: Testing
dependencies:
- drupal:image
- drupal:media_library
- drupal:media_test_source
- drupal:menu_ui
- drupal:node
- drupal:path
# Information added by Drupal.org packaging script on 2024-07-04
version: '10.3.1'
project: 'drupal'
datestamp: 1720094222

View File

@@ -0,0 +1,51 @@
<?php
/**
* @file
* Contains hook implementations for the media_library_test module.
*/
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\media_library_test\Form\TestNodeFormOverride;
/**
* Implements hook_ENTITY_TYPE_create_access().
*/
function media_library_test_media_create_access(AccountInterface $account, array $context, $entity_bundle) {
if (isset($context['media_library_state'])) {
/** @var \Drupal\media_library\MediaLibraryState $state */
$state = $context['media_library_state'];
return AccessResult::forbiddenIf($state->getSelectedTypeId() === 'deny_access');
}
return AccessResult::neutral();
}
/**
* Implements hook_entity_field_access().
*/
function media_library_test_entity_field_access($operation, FieldDefinitionInterface $field_definition, AccountInterface $account, ?FieldItemListInterface $items = NULL) {
$deny_fields = \Drupal::state()->get('media_library_test_entity_field_access_deny_fields', []);
// Always deny the field_media_no_access field.
$deny_fields[] = 'field_media_no_access';
return AccessResult::forbiddenIf(in_array($field_definition->getName(), $deny_fields, TRUE), 'Field access denied by test module');
}
/**
* Implements hook_entity_type_alter().
*/
function media_library_test_entity_type_alter(array &$entity_types) {
if (isset($entity_types['node'])) {
$entity_types['node']->setFormClass('default', TestNodeFormOverride::class);
$entity_types['node']->setFormClass('edit', TestNodeFormOverride::class);
}
}
/**
* Implements hook_field_widget_info_alter().
*/
function media_library_test_field_widget_info_alter(array &$info) {
$info['media_library_widget']['field_types'][] = 'entity_reference_subclass';
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Drupal\media_library_test\Form;
use Drupal\Core\Form\FormStateInterface;
use Drupal\node\NodeForm;
/**
* Override NodeForm to test media library form submission semantics.
*/
class TestNodeFormOverride extends NodeForm {
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$triggering_element = $form_state->getTriggeringElement();
if (in_array('open_button', $triggering_element['#parents'], TRUE)) {
throw new \Exception('The media library widget open_button element should not trigger form submit.');
}
parent::submitForm($form, $form_state);
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace Drupal\media_library_test\Plugin\Field\FieldType;
use Drupal\Core\Field\Attribute\FieldType;
use Drupal\Core\Field\EntityReferenceFieldItemList;
use Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem;
use Drupal\Core\StringTranslation\TranslatableMarkup;
/**
* Plugin implementation of the 'entity_reference_subclass' field type.
*/
#[FieldType(
id: "entity_reference_subclass",
label: new TranslatableMarkup("Entity reference subclass"),
description: new TranslatableMarkup("An entity field containing an entity reference."),
category: "reference",
default_widget: "entity_reference_autocomplete",
default_formatter: "entity_reference_label",
list_class: EntityReferenceFieldItemList::class,
)]
class EntityReferenceItemSubclass extends EntityReferenceItem {
}

View File

@@ -0,0 +1,13 @@
name: 'Media Library test widget'
type: module
description: 'Test widget that has a nested media library widget'
package: Testing
dependencies:
- drupal:image
- drupal:media_library
- drupal:media_test_source
# Information added by Drupal.org packaging script on 2024-07-04
version: '10.3.1'
project: 'drupal'
datestamp: 1720094222

View File

@@ -0,0 +1,58 @@
<?php
namespace Drupal\media_library_test_widget\Plugin\Field\FieldWidget;
use Drupal\Core\Field\Attribute\FieldWidget;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\media_library\Plugin\Field\FieldWidget\MediaLibraryWidget;
/**
* Plugin implementation of the 'media_library_inception_widget' widget.
*
* This widget is used to simulate the media library widget nested inside
* another widget that performs validation of required fields before there is
* an opportunity to add media.
*/
#[FieldWidget(
id: 'media_library_inception_widget',
label: new TranslatableMarkup('Media library inception widget'),
description: new TranslatableMarkup('Puts a widget in a widget for testing purposes.'),
field_types: ['entity_reference'],
multiple_values: TRUE,
)]
class MediaLibraryInceptionWidget extends MediaLibraryWidget {
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
if (empty($element['#element_validate'])) {
$element['#element_validate'] = [];
}
$element['#element_validate'][] = [$this, 'elementValidate'];
return parent::formElement($items, $delta, $element, $form, $form_state);
}
/**
* {@inheritdoc}
*/
public function elementValidate($element, FormStateInterface $form_state, $form) {
$field_name = $element['#field_name'];
$entity = $form_state->getFormObject()->getEntity();
$input = $form_state->getUserInput();
if (!empty($input['_triggering_element_name']) && str_contains($input['_triggering_element_name'], 'media-library-update')) {
// This will validate a required field before an upload is completed.
$display = EntityFormDisplay::collectRenderDisplay($entity, 'edit');
$display->extractFormValues($entity, $form, $form_state);
$display->validateFormValues($entity, $form, $form_state);
}
$form_value = $form_state->getValue($field_name);
if (!empty($form_value['media_library_selection'])) {
$entity->set($field_name, $form_value['media_library_selection']);
}
}
}

View File

@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media_library\Functional;
use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
/**
* Generic module test for media_library.
*
* @group media_library
*/
class GenericTest extends GenericModuleTestBase {}

View File

@@ -0,0 +1,282 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media_library\Functional;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\image\Entity\ImageStyle;
use Drupal\media\Plugin\media\Source\File;
use Drupal\media\Plugin\media\Source\Image;
use Drupal\Tests\BrowserTestBase;
use Drupal\Tests\media\Traits\MediaTypeCreationTrait;
use Drupal\media\Entity\MediaType;
/**
* Tests that the Media Library automatically configures form/view modes.
*
* @group media_library
*/
class MediaLibraryDisplayModeTest extends BrowserTestBase {
use MediaTypeCreationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'field_ui',
'media',
'system',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->drupalLogin($this->drupalCreateUser([
'access media overview',
'administer media',
'administer media fields',
'administer media form display',
'administer media display',
'administer media types',
'view media',
]));
}
/**
* Tests that the Media Library can automatically configure display modes.
*/
public function testDisplayModes(): void {
$this->createMediaType('file', [
'id' => 'type_one',
]);
$this->createMediaType('file', [
'id' => 'type_two',
'field_map' => ['name' => File::METADATA_ATTRIBUTE_NAME],
]);
$this->createMediaType('image', [
'id' => 'type_three',
]);
$this->createMediaType('image', [
'id' => 'type_four',
'field_map' => ['name' => Image::METADATA_ATTRIBUTE_NAME],
]);
// Display modes are not automatically created when creating a media type
// programmatically, only when installing the module or when creating a
// media type via the UI.
$this->assertNull(EntityFormDisplay::load('media.type_one.media_library'));
$this->assertNull(EntityViewDisplay::load('media.type_one.media_library'));
$this->assertNull(EntityFormDisplay::load('media.type_two.media_library'));
$this->assertNull(EntityViewDisplay::load('media.type_two.media_library'));
$this->assertNull(EntityFormDisplay::load('media.type_three.media_library'));
$this->assertNull(EntityViewDisplay::load('media.type_three.media_library'));
$this->assertNull(EntityFormDisplay::load('media.type_four.media_library'));
$this->assertNull(EntityViewDisplay::load('media.type_four.media_library'));
// Display modes are created on install.
$this->container->get('module_installer')->install(['media_library']);
// The container was rebuilt during module installation, so ensure we have
// an up-to-date reference to it.
$this->container = $this->kernel->getContainer();
// For a non-image media type without a mapped name field, the media_library
// form mode should only contain the name field.
$this->assertFormDisplay('type_one', TRUE, FALSE);
$this->assertViewDisplay('type_one', 'medium');
// For a non-image media type with a mapped name field, the media_library
// form mode should not contain any fields.
$this->assertFormDisplay('type_two', FALSE, FALSE);
$this->assertViewDisplay('type_two', 'medium');
// For an image media type without a mapped name field, the media_library
// form mode should contain the name field and the source field.
$this->assertFormDisplay('type_three', TRUE, TRUE);
$this->assertViewDisplay('type_three', 'medium');
// For an image media type with a mapped name field, the media_library form
// mode should only contain the source field.
$this->assertFormDisplay('type_four', FALSE, TRUE);
$this->assertViewDisplay('type_four', 'medium');
// Create a non-image media type without a mapped name field in the UI.
$type_five_id = 'type_five';
$edit = [
'label' => $type_five_id,
'id' => $type_five_id,
'source' => 'file',
];
$this->drupalGet('admin/structure/media/add');
$this->submitForm($edit, 'Save and manage fields');
$this->submitForm([], 'Save and manage fields');
$this->assertSession()->pageTextContains("Media Library form and view displays have been created for the $type_five_id media type.");
$this->assertFormDisplay($type_five_id, TRUE, FALSE);
$this->assertViewDisplay($type_five_id, 'medium');
// Create a non-image media type with a mapped name field in the UI.
$type_six_id = 'type_six';
$edit = [
'label' => $type_six_id,
'id' => $type_six_id,
'source' => 'file',
];
$this->drupalGet('admin/structure/media/add');
$this->submitForm($edit, 'Save');
$edit = [
'field_map[name]' => File::METADATA_ATTRIBUTE_NAME,
];
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains("Media Library form and view displays have been created for the $type_six_id media type.");
$this->assertFormDisplay($type_six_id, FALSE, FALSE);
$this->assertViewDisplay($type_six_id, 'medium');
// Create an image media type without a mapped name field in the UI.
$type_seven_id = 'type_seven';
$edit = [
'label' => $type_seven_id,
'id' => $type_seven_id,
'source' => 'image',
];
$this->drupalGet('admin/structure/media/add');
$this->submitForm($edit, 'Save');
$this->submitForm([], 'Save');
$this->assertSession()->pageTextContains("Media Library form and view displays have been created for the $type_seven_id media type.");
$this->assertFormDisplay($type_seven_id, TRUE, TRUE);
$this->assertViewDisplay($type_seven_id, 'medium');
// Create an image media type with a mapped name field in the UI.
$type_eight_id = 'type_eight';
$edit = [
'label' => $type_eight_id,
'id' => $type_eight_id,
'source' => 'image',
];
$this->drupalGet('admin/structure/media/add');
$this->submitForm($edit, 'Save');
$edit = [
'field_map[name]' => Image::METADATA_ATTRIBUTE_NAME,
];
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains("Media Library form and view displays have been created for the $type_eight_id media type.");
$this->assertFormDisplay($type_eight_id, FALSE, TRUE);
$this->assertViewDisplay($type_eight_id, 'medium');
// Create an oEmbed media type with a mapped name field in the UI.
$type_id = 'pinto_bean';
$edit = [
'label' => $type_id,
'id' => $type_id,
'source' => 'oembed:video',
];
$this->drupalGet('admin/structure/media/add');
$this->submitForm($edit, 'Save');
$edit = [
'field_map[title]' => 'name',
];
$this->submitForm($edit, 'Save');
$this->assertSession()->pageTextContains("Media Library form and view displays have been created for the $type_id media type.");
$this->assertFormDisplay($type_id, FALSE, FALSE);
$this->assertViewDisplay($type_id, 'medium');
// Now that all our media types have been created, ensure the bundle info
// cache is up-to-date.
$this->container->get('entity_type.bundle.info')->clearCachedBundles();
// Delete a form and view display.
EntityFormDisplay::load('media.type_one.media_library')->delete();
EntityViewDisplay::load('media.type_one.media_library')->delete();
// Make sure the form and view display are not created when saving existing
// media types.
$this->drupalGet('admin/structure/media/manage/type_one');
$this->submitForm([], 'Save');
$this->assertNull(EntityFormDisplay::load('media.type_one.media_library'));
$this->assertNull(EntityViewDisplay::load('media.type_one.media_library'));
// Delete the medium image style.
ImageStyle::load('medium')->delete();
// Create an image media type, assert the displays are created and the
// fallback 'media_library' image style is used.
$type_nine_id = 'type_nine';
$edit = [
'label' => $type_nine_id,
'id' => $type_nine_id,
'source' => 'image',
];
$this->drupalGet('admin/structure/media/add');
$this->submitForm($edit, 'Save');
$this->submitForm([], 'Save');
$this->assertSession()->pageTextContains("Media Library form and view displays have been created for the $type_nine_id media type.");
$this->assertFormDisplay($type_nine_id, TRUE, TRUE);
$this->assertViewDisplay($type_nine_id, 'media_library');
}
/**
* Asserts the media library form display components for a media type.
*
* @param string $type_id
* The media type ID.
* @param bool $has_name
* Whether the media library form display should contain the name field or
* not.
* @param bool $has_source_field
* Whether the media library form display should contain the source field or
* not.
*
* @internal
*/
protected function assertFormDisplay(string $type_id, bool $has_name, bool $has_source_field): void {
// These components are added by default and invisible.
$components = [
'revision_log_message',
'langcode',
];
// Only assert the name and source field if needed.
if ($has_name) {
$components[] = 'name';
}
if ($has_source_field) {
$type = MediaType::load($type_id);
$components[] = $type->getSource()->getSourceFieldDefinition($type)->getName();
}
$form_display = EntityFormDisplay::load('media.' . $type_id . '.media_library');
$this->assertInstanceOf(EntityFormDisplay::class, $form_display);
$actual_components = array_keys($form_display->getComponents());
sort($components);
sort($actual_components);
$this->assertSame($components, $actual_components);
}
/**
* Asserts the media library view display components for a media type.
*
* @param string $type_id
* The media type ID.
* @param string $image_style
* The ID of the image style that should be configured for the thumbnail.
*
* @internal
*/
protected function assertViewDisplay(string $type_id, string $image_style): void {
$view_display = EntityViewDisplay::load('media.' . $type_id . '.media_library');
$this->assertInstanceOf(EntityViewDisplay::class, $view_display);
// Assert the media library view display contains only the thumbnail.
$this->assertSame(['thumbnail'], array_keys($view_display->getComponents()));
// Assert the thumbnail image style.
$thumbnail = $view_display->getComponent('thumbnail');
$this->assertIsArray($thumbnail);
$this->assertSame($image_style, $thumbnail['settings']['image_style']);
}
}

View File

@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media_library\Functional;
use Drupal\image\Entity\ImageStyle;
use Drupal\Tests\BrowserTestBase;
/**
* Tests access to the Media library image style.
*
* @group media_library
*/
class MediaLibraryImageStyleAccessTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['media_library'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests that users can't delete the 'media_library' image style.
*/
public function testMediaLibraryImageStyleAccess(): void {
// Create a user who can manage the image styles.
$user = $this->createUser([
'access administration pages',
'administer image styles',
]);
// The user should be able to delete the 'medium' image style, but not the
// 'media_library' image style.
$medium = ImageStyle::load('medium');
$this->assertTrue($medium->access('delete', $user));
$mediaLibrary = ImageStyle::load('media_library');
$this->assertFalse($mediaLibrary->access('delete', $user));
$this->drupalLogin($user);
$this->drupalGet($medium->toUrl('delete-form'));
$this->assertSession()->statusCodeEquals(200);
$this->drupalGet($mediaLibrary->toUrl('delete-form'));
$this->assertSession()->statusCodeEquals(403);
}
}

View File

@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media_library\Functional;
use Drupal\Tests\BrowserTestBase;
/**
* Tests the Media Library settings form.
*
* @coversDefaultClass \Drupal\media_library\Form\SettingsForm
* @group media_library
*/
class SettingsFormTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['media_library'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests the Media Library settings form.
*/
public function testSettingsForm(): void {
$account = $this->drupalCreateUser([
'access administration pages',
'administer media',
]);
$this->drupalLogin($account);
$page = $this->getSession()->getPage();
$assert_session = $this->assertSession();
$this->drupalGet('/admin/config');
$page->clickLink('Media Library settings');
$page->checkField('Enable advanced UI');
$page->pressButton('Save configuration');
$assert_session->checkboxChecked('Enable advanced UI');
$page->uncheckField('Enable advanced UI');
$page->pressButton('Save configuration');
$assert_session->checkboxNotChecked('Enable advanced UI');
}
}

View File

@@ -0,0 +1,349 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media_library\FunctionalJavascript;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\file\Entity\File;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
use Drupal\media\Entity\Media;
use Drupal\Tests\content_moderation\Traits\ContentModerationTestTrait;
use Drupal\Tests\field\Traits\EntityReferenceFieldCreationTrait;
use Drupal\Tests\media\Traits\MediaTypeCreationTrait;
use Drupal\Tests\TestFileCreationTrait;
// cspell:ignore hoglet
/**
* Tests media library integration with content moderation.
*
* @group media_library
*/
class ContentModerationTest extends WebDriverTestBase {
use ContentModerationTestTrait;
use EntityReferenceFieldCreationTrait;
use MediaTypeCreationTrait;
use TestFileCreationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'content_moderation',
'field',
'media',
'media_library',
'node',
'views',
];
/**
* {@inheritdoc}
*
* @todo Remove and fix test to not rely on super user.
* @see https://www.drupal.org/project/drupal/issues/3437620
*/
protected bool $usesSuperUserAccessPolicy = TRUE;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* User with the 'administer media' permission.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $userAdmin;
/**
* User with the 'view media' permission.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $userViewer;
/**
* User with the 'view media' and 'view own unpublished media' permissions.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $userViewOwnUnpublished;
/**
* User with the 'view media' and 'view any unpublished content' permissions.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $userViewAnyUnpublished;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Create an image media type and article node type.
$this->createMediaType('image', ['id' => 'image']);
$this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
// Create a media reference field on articles.
$this->createEntityReferenceField(
'node',
'article',
'field_media',
'Media',
'media',
'default',
['target_bundles' => ['image']],
FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED
);
// Add the media field to the form display.
$form_display = \Drupal::service('entity_display.repository')->getFormDisplay('node', 'article', 'default');
$form_display->setComponent('field_media', [
'type' => 'media_library_widget',
])->save();
// Configure the "Editorial" workflow to apply to image media.
$workflow = $this->createEditorialWorkflow();
$workflow->getTypePlugin()->addEntityTypeAndBundle('media', 'image');
$workflow->save();
$image = File::create([
'uri' => $this->getTestFiles('image')[0]->uri,
]);
$image->setPermanent();
$image->save();
// Create a draft, published and archived media item.
$draft_media = Media::create([
'name' => 'Hoglet',
'bundle' => 'image',
'field_media_image' => $image,
'moderation_state' => 'draft',
]);
$draft_media->save();
$published_media = Media::create([
'name' => 'Panda',
'bundle' => 'image',
'field_media_image' => $image,
'moderation_state' => 'published',
]);
$published_media->save();
$archived_media = Media::create([
'name' => 'Mammoth',
'bundle' => 'image',
'field_media_image' => $image,
'moderation_state' => 'archived',
]);
$archived_media->save();
// Create some users for our tests. We want to check with user 1, a media
// administrator with 'administer media' permissions, a user that has the
// 'view media' permissions, a user that can 'view media' and 'view own
// unpublished media', and a user that has 'view media' and 'view any
// unpublished content' permissions.
$this->userAdmin = $this->drupalCreateUser([
'access administration pages',
'access content',
'access media overview',
'edit own article content',
'create article content',
'administer media',
]);
$this->userViewer = $this->drupalCreateUser([
'access administration pages',
'access content',
'access media overview',
'edit own article content',
'create article content',
'view media',
'create media',
]);
$this->userViewOwnUnpublished = $this->drupalCreateUser([
'access administration pages',
'access content',
'access media overview',
'edit own article content',
'create article content',
'view media',
'view own unpublished media',
'create media',
]);
$this->userViewAnyUnpublished = $this->drupalCreateUser([
'access administration pages',
'access content',
'access media overview',
'edit own article content',
'create article content',
'view media',
'create media',
'view any unpublished content',
]);
}
/**
* Tests the media library widget only shows published media.
*/
public function testAdministrationPage(): void {
// User 1 should be able to see all media items.
$this->drupalLogin($this->rootUser);
$this->drupalGet('admin/content/media');
$this->assertAllMedia();
// The media admin user should be able to see all media items.
$this->drupalLogin($this->userAdmin);
$this->drupalGet('admin/content/media');
$this->assertAllMedia();
// The media viewer user should be able to see only published media items.
$this->drupalLogin($this->userViewer);
$this->drupalGet('admin/content/media');
$this->assertOnlyPublishedMedia();
// The media viewer user that can also view its own unpublished media should
// also be able to see only published media items since it is not the owner
// of the created media items.
$this->drupalLogin($this->userViewOwnUnpublished);
$this->drupalGet('admin/content/media');
$this->assertOnlyPublishedMedia();
// When content moderation is enabled, a media viewer that can view any
// unpublished content should be able to see all media.
// @see content_moderation_entity_access()
$this->drupalLogin($this->userViewAnyUnpublished);
$this->drupalGet('admin/content/media');
$this->assertAllMedia();
// Assign all media to the user with the 'view own unpublished media'
// permission.
foreach (Media::loadMultiple() as $media) {
$media->setOwner($this->userViewOwnUnpublished);
$media->save();
}
// User 1 should still be able to see all media items.
$this->drupalLogin($this->rootUser);
$this->drupalGet('admin/content/media');
$this->assertAllMedia();
// The media admin user should still be able to see all media items.
$this->drupalLogin($this->userAdmin);
$this->drupalGet('admin/content/media');
$this->assertAllMedia();
// The media viewer user should still be able to see only published media
// items.
$this->drupalLogin($this->userViewer);
$this->drupalGet('admin/content/media');
$this->assertOnlyPublishedMedia();
// The media viewer user that can also view its own unpublished media
// should now be able to see all media items since it is the owner of the
// created media items.
$this->drupalLogin($this->userViewOwnUnpublished);
$this->drupalGet('admin/content/media');
$this->assertAllMedia();
// The media viewer that can view any unpublished content should still be
// able to see all media.
$this->drupalLogin($this->userViewAnyUnpublished);
$this->drupalGet('admin/content/media');
$this->assertAllMedia();
}
/**
* Tests the media library widget only shows published media.
*/
public function testWidget(): void {
$assert_session = $this->assertSession();
// All users should only be able to see published media items.
$this->drupalLogin($this->rootUser);
$this->drupalGet('node/add/article');
$assert_session->elementExists('css', '.js-media-library-open-button[name^="field_media"]')->click();
$assert_session->assertWaitOnAjaxRequest();
$this->assertOnlyPublishedMedia();
$this->drupalLogin($this->userAdmin);
$this->drupalGet('node/add/article');
$assert_session->elementExists('css', '.js-media-library-open-button[name^="field_media"]')->click();
$assert_session->assertWaitOnAjaxRequest();
$this->assertOnlyPublishedMedia();
$this->drupalLogin($this->userViewer);
$this->drupalGet('node/add/article');
$assert_session->elementExists('css', '.js-media-library-open-button[name^="field_media"]')->click();
$assert_session->assertWaitOnAjaxRequest();
$this->assertOnlyPublishedMedia();
$this->drupalLogin($this->userViewOwnUnpublished);
$this->drupalGet('node/add/article');
$assert_session->elementExists('css', '.js-media-library-open-button[name^="field_media"]')->click();
$assert_session->assertWaitOnAjaxRequest();
$this->assertOnlyPublishedMedia();
$this->drupalLogin($this->userViewAnyUnpublished);
$this->drupalGet('node/add/article');
$assert_session->elementExists('css', '.js-media-library-open-button[name^="field_media"]')->click();
$assert_session->assertWaitOnAjaxRequest();
$this->assertOnlyPublishedMedia();
// After we change the owner to the user with 'view own unpublished media'
// permission, all users should still only be able to see published media.
foreach (Media::loadMultiple() as $media) {
$media->setOwner($this->userViewOwnUnpublished);
$media->save();
}
$this->drupalLogin($this->rootUser);
$this->drupalGet('node/add/article');
$assert_session->elementExists('css', '.js-media-library-open-button[name^="field_media"]')->click();
$assert_session->assertWaitOnAjaxRequest();
$this->assertOnlyPublishedMedia();
$this->drupalLogin($this->userAdmin);
$this->drupalGet('node/add/article');
$assert_session->elementExists('css', '.js-media-library-open-button[name^="field_media"]')->click();
$assert_session->assertWaitOnAjaxRequest();
$this->assertOnlyPublishedMedia();
$this->drupalLogin($this->userViewer);
$this->drupalGet('node/add/article');
$assert_session->elementExists('css', '.js-media-library-open-button[name^="field_media"]')->click();
$assert_session->assertWaitOnAjaxRequest();
$this->assertOnlyPublishedMedia();
$this->drupalLogin($this->userViewOwnUnpublished);
$this->drupalGet('node/add/article');
$assert_session->elementExists('css', '.js-media-library-open-button[name^="field_media"]')->click();
$assert_session->assertWaitOnAjaxRequest();
$this->assertOnlyPublishedMedia();
$this->drupalLogin($this->userViewAnyUnpublished);
$this->drupalGet('node/add/article');
$assert_session->elementExists('css', '.js-media-library-open-button[name^="field_media"]')->click();
$assert_session->assertWaitOnAjaxRequest();
$this->assertOnlyPublishedMedia();
}
/**
* Asserts all media items are visible.
*
* @internal
*/
protected function assertAllMedia(): void {
$assert_session = $this->assertSession();
$assert_session->pageTextContains('Hoglet');
$assert_session->pageTextContains('Panda');
$assert_session->pageTextContains('Mammoth');
}
/**
* Asserts only published media items are visible.
*
* @internal
*/
protected function assertOnlyPublishedMedia(): void {
$assert_session = $this->assertSession();
$assert_session->pageTextNotContains('Hoglet');
$assert_session->pageTextContains('Panda');
$assert_session->pageTextNotContains('Mammoth');
}
}

View File

@@ -0,0 +1,168 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media_library\FunctionalJavascript;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
use Drupal\media\Entity\Media;
use Drupal\Tests\TestFileCreationTrait;
/**
* Tests media widget nested inside another widget.
*
* @group media_library
*/
class EmbeddedFormWidgetTest extends WebDriverTestBase {
use TestFileCreationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'media_library',
'media_library_test',
'media_library_test_widget',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$display_repository = $this->container->get('entity_display.repository');
FieldStorageConfig::create([
'field_name' => 'media_image_field',
'entity_type' => 'node',
'type' => 'entity_reference',
'settings' => [
'target_type' => 'media',
'required' => TRUE,
],
])->save();
FieldConfig::create([
'label' => 'A Media Image Field',
'field_name' => 'media_image_field',
'entity_type' => 'node',
'bundle' => 'basic_page',
'field_type' => 'entity_reference',
'required' => TRUE,
'settings' => [
'handler_settings' => [
'target_bundles' => [
'type_three' => 'type_three',
],
],
],
])->save();
$display_repository->getFormDisplay('node', 'basic_page')
->setComponent('media_image_field', [
'type' => 'media_library_widget',
'region' => 'content',
'settings' => [
'media_types' => ['type_three'],
],
])
->save();
$this->config('media_library.settings')
->set('advanced_ui', TRUE)
->save();
$user = $this->drupalCreateUser([
'access content',
'access media overview',
'edit own basic_page content',
'create basic_page content',
'create media',
'view media',
]);
$this->drupalLogin($user);
}
/**
* Tests media inside another widget that validates too enthusiastically.
*
* @dataProvider insertionReselectionProvider
*/
public function testInsertionAndReselection($widget): void {
$this->container
->get('entity_display.repository')
->getFormDisplay('node', 'basic_page')
->setComponent('media_image_field', [
'type' => $widget,
'region' => 'content',
'settings' => [
'media_types' => ['type_three'],
],
])
->save();
$page = $this->getSession()->getPage();
$assert_session = $this->assertSession();
foreach ($this->getTestFiles('image') as $image) {
$extension = pathinfo($image->filename, PATHINFO_EXTENSION);
if ($extension === 'jpg') {
$jpg_image = $image;
break;
}
}
$this->drupalGet('node/add/basic_page');
$wrapper = $assert_session->elementExists('css', '#media_image_field-media-library-wrapper');
$wrapper->pressButton('Add media');
$this->assertNotNull($assert_session->waitForText('Add or select media'));
$page->attachFileToField('Add file', $this->container->get('file_system')->realpath($jpg_image->uri));
$this->assertNotNull($assert_session->waitForText('Alternative text'));
$page->fillField('Alternative text', $this->randomString());
$assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Save and insert');
$first_item_locator = "(//div[@data-drupal-selector='edit-media-image-field-selection-0'])[1]";
$this->assertNotNull($first_item = $assert_session->waitForElementVisible('xpath', $first_item_locator));
$first_item->pressButton('Remove');
$assert_session->waitForElementRemoved('xpath', $first_item_locator);
$page->waitFor(10, function () use ($wrapper) {
return $wrapper->hasButton('Add media');
});
// Test reinserting the same selection.
$media_items = Media::loadMultiple();
$added_media = array_pop($media_items);
$added_media_id = $added_media->id();
$wrapper->pressButton('Add media');
$this->assertNotNull($assert_session->waitForText('Add or select media'));
$assert_session->elementExists('xpath', "(//div[contains(@class, 'media-library-item')])[1]")->click();
$assert_session->checkboxChecked("media_library_select_form[$added_media_id]");
$assert_session->elementExists('css', '.ui-dialog-buttonpane')->pressButton('Insert selected');
$this->assertNotNull($assert_session->waitForElementVisible('xpath', $first_item_locator));
}
/**
* Data provider for ::testInsertionAndReselection().
*
* @return array
* Test data.
*/
public static function insertionReselectionProvider() {
return [
'using media_library_widget' => [
'widget' => 'media_library_widget',
],
'using media_library_inception_widget' => [
'widget' => 'media_library_inception_widget',
],
];
}
}

View File

@@ -0,0 +1,653 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media_library\FunctionalJavascript;
use Drupal\field\Entity\FieldConfig;
use Drupal\FunctionalJavascriptTests\SortableTestTrait;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
/**
* Tests the Media library entity reference widget.
*
* @group media_library
*/
class EntityReferenceWidgetTest extends MediaLibraryTestBase {
use SortableTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['field_ui'];
/**
* The theme to install as the default for testing.
*
* @var string
*/
protected $defaultTheme = 'starterkit_theme';
/**
* Test media items.
*
* @var \Drupal\media\MediaInterface[]
*/
protected $mediaItems = [];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Create a few example media items for use in selection.
$this->mediaItems = $this->createMediaItems([
'type_one' => [
'Horse',
'Bear',
'Cat',
'Dog',
],
'type_two' => [
'Crocodile',
'Lizard',
'Snake',
'Turtle',
],
]);
// Create a user who can use the Media library.
$user = $this->drupalCreateUser([
'access content',
'create basic_page content',
'edit own basic_page content',
'view media',
'create media',
'administer node form display',
]);
$this->drupalLogin($user);
}
/**
* Tests that disabled media items don't capture focus on page load.
*/
public function testFocusNotAppliedWithoutSelectionChange(): void {
// Create a node with the maximum number of values for the field_twin_media
// field.
$node = $this->drupalCreateNode([
'type' => 'basic_page',
'field_twin_media' => [
$this->mediaItems['Horse'],
$this->mediaItems['Bear'],
],
]);
$this->drupalGet($node->toUrl('edit-form'));
$open_button = $this->assertElementExistsAfterWait('css', '.js-media-library-open-button[name^="field_twin_media"]');
// The open button should be disabled, but not have the
// 'data-disabled-focus' attribute.
$this->assertFalse($open_button->hasAttribute('data-disabled-focus'));
$this->assertTrue($open_button->hasAttribute('disabled'));
// The button should be disabled.
$this->assertJsCondition('jQuery("#field_twin_media-media-library-wrapper .js-media-library-open-button").is(":disabled")');
// The button should not have focus.
$this->assertJsCondition('jQuery("#field_twin_media-media-library-wrapper .js-media-library-open-button").not(":focus")');
}
/**
* Tests that the Media library's widget works as expected.
*/
public function testWidget(): void {
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
// Visit a node create page.
$this->drupalGet('node/add/basic_page');
// Assert that media widget instances are present.
$assert_session->pageTextContains('Unlimited media');
$assert_session->pageTextContains('Twin media');
$assert_session->pageTextContains('Single media type');
$assert_session->pageTextContains('Empty types media');
// Assert generic media library elements.
$this->openMediaLibraryForField('field_unlimited_media');
$assert_session->elementExists('css', '.ui-dialog-titlebar-close')->click();
// Assert that the media type menu is available when more than 1 type is
// configured for the field.
$menu = $this->openMediaLibraryForField('field_unlimited_media');
$this->assertTrue($menu->hasLink('Show Type One media (selected)'));
$this->assertFalse($menu->hasLink('Type Two'));
$this->assertTrue($menu->hasLink('Type Three'));
$this->assertFalse($menu->hasLink('Type Four'));
$this->switchToMediaType('Three');
// Assert the active tab is set correctly.
$this->assertFalse($menu->hasLink('Show Type One media (selected)'));
$this->assertTrue($menu->hasLink('Show Type Three media (selected)'));
// Assert the focus is set to the first tabbable element when a vertical tab
// is clicked.
$this->assertJsCondition('jQuery(tabbable.tabbable(document.getElementById("media-library-content"))[0]).is(":focus")');
$assert_session->elementExists('css', '.ui-dialog-titlebar-close')->click();
// Assert that there are no links in the media library view.
$this->openMediaLibraryForField('field_unlimited_media');
$assert_session->elementNotExists('css', '.media-library-item__name a');
$assert_session->elementNotExists('css', '.view-media-library .media-library-item__edit');
$assert_session->elementNotExists('css', '.view-media-library .media-library-item__remove');
$assert_session->elementExists('css', '.ui-dialog-titlebar-close')->click();
// Assert that the media type menu is available when the target_bundles
// setting for the entity reference field is null. All types should be
// allowed in this case.
$menu = $this->openMediaLibraryForField('field_null_types_media');
// Assert that the button to open the media library does not submit the
// parent form. We can do this by checking if the validation of the parent
// form is not triggered.
$assert_session->pageTextNotContains('Title field is required.');
$this->assertTrue($menu->hasLink('Type One'));
$this->assertTrue($menu->hasLink('Type Two'));
$this->assertTrue($menu->hasLink('Type Three'));
$this->assertTrue($menu->hasLink('Type Four'));
$this->assertTrue($menu->hasLink('Type Five'));
// Insert media to test validation with null target_bundles.
$this->switchToMediaType('One');
$this->assertAnnounceContains('Showing Type One media.');
$this->selectMediaItem(0);
$this->pressInsertSelected('Added one media item.');
// Assert that the media type menu is not available when only 1 type is
// configured for the field.
$this->openMediaLibraryForField('field_single_media_type', '#media-library-wrapper');
$this->waitForElementTextContains('.media-library-selected-count', '0 of 1 item selected');
// Select a media item, assert the hidden selection field contains the ID of
// the selected item.
$this->selectMediaItem(0);
$assert_session->hiddenFieldValueEquals('media-library-modal-selection', '4');
$this->assertSelectedMediaCount('1 of 1 item selected');
$assert_session->elementNotExists('css', '.js-media-library-menu');
$assert_session->elementExists('css', '.ui-dialog-titlebar-close')->click();
// Assert the menu links can be sorted through the widget configuration.
$this->openMediaLibraryForField('field_twin_media');
$links = $this->getTypesMenu()->findAll('css', 'a');
$link_titles = [];
foreach ($links as $link) {
$link_titles[] = $link->getText();
}
$expected_link_titles = ['Show Type Three media (selected)', 'Show Type One media', 'Show Type Two media', 'Show Type Four media'];
$this->assertSame($link_titles, $expected_link_titles);
$this->drupalGet('admin/structure/types/manage/basic_page/form-display');
// Ensure that the widget settings form is not displayed when only
// one media type is allowed.
$assert_session->pageTextContains('Single media type');
$assert_session->buttonNotExists('field_single_media_type_settings_edit');
$assert_session->buttonExists('field_twin_media_settings_edit')->press();
$this->assertElementExistsAfterWait('css', '#field-twin-media .tabledrag-toggle-weight')->press();
$assert_session->fieldExists('fields[field_twin_media][settings_edit_form][settings][media_types][type_one][weight]')->selectOption('0');
$assert_session->fieldExists('fields[field_twin_media][settings_edit_form][settings][media_types][type_three][weight]')->selectOption('1');
$assert_session->fieldExists('fields[field_twin_media][settings_edit_form][settings][media_types][type_four][weight]')->selectOption('2');
$assert_session->fieldExists('fields[field_twin_media][settings_edit_form][settings][media_types][type_two][weight]')->selectOption('3');
$assert_session->buttonExists('Save')->press();
$this->drupalGet('node/add/basic_page');
$this->openMediaLibraryForField('field_twin_media');
$link_titles = array_map(function ($link) {
return $link->getText();
}, $links);
$this->assertSame($link_titles, ['Show Type One media (selected)', 'Show Type Three media', 'Show Type Four media', 'Show Type Two media']);
$assert_session->elementExists('css', '.ui-dialog-titlebar-close')->click();
// Assert the announcements for media type navigation in the media library.
$this->openMediaLibraryForField('field_unlimited_media');
$this->switchToMediaType('Three');
$this->assertAnnounceContains('Showing Type Three media.');
$this->switchToMediaType('One');
$this->assertAnnounceContains('Showing Type One media.');
// Assert the links can be triggered by via the space bar.
$assert_session->elementExists('named', ['link', 'Type Three'])->keyPress(32);
$this->assertAnnounceContains('Showing Type Three media.');
$assert_session->elementExists('css', '.ui-dialog-titlebar-close')->click();
// Assert media is only visible on the tab for the related media type.
$this->openMediaLibraryForField('field_unlimited_media');
$assert_session->pageTextContains('Dog');
$assert_session->pageTextContains('Bear');
$assert_session->pageTextNotContains('Turtle');
$this->switchToMediaType('Three');
$this->assertAnnounceContains('Showing Type Three media.');
$assert_session->elementExists('named', ['link', 'Show Type Three media (selected)']);
$assert_session->pageTextNotContains('Dog');
$assert_session->pageTextNotContains('Bear');
$assert_session->pageTextNotContains('Turtle');
$assert_session->elementExists('css', '.ui-dialog-titlebar-close')->click();
// Assert the exposed name filter of the view.
$this->openMediaLibraryForField('field_unlimited_media');
$session = $this->getSession();
$session->getPage()->fillField('Name', 'Dog');
$session->getPage()->pressButton('Apply filters');
$this->waitForText('Dog');
$this->markTestSkipped("Skipped temporarily for random fails.");
$this->waitForNoText('Bear');
$session->getPage()->fillField('Name', '');
$session->getPage()->pressButton('Apply filters');
$this->waitForText('Dog');
$this->waitForText('Bear');
$assert_session->elementExists('css', '.ui-dialog-titlebar-close')->click();
// Assert adding a single media item and removing it.
$this->openMediaLibraryForField('field_twin_media');
$this->selectMediaItem(0);
$this->pressInsertSelected('Added one media item.');
// Assert the focus is set back on the open button of the media field.
$this->assertJsCondition('jQuery("#field_twin_media-media-library-wrapper .js-media-library-open-button").is(":focus")');
// The toggle for weight inputs' visibility should not be available when the
// field contains a single item.
$wrapper = $assert_session->elementExists('css', '.field--name-field-twin-media');
$assert_session->elementNotExists('named', ['button', 'Show media item weights'], $wrapper);
// Remove the selected item.
$button = $assert_session->buttonExists('Remove', $wrapper);
$this->assertSame('Remove Dog', $button->getAttribute('aria-label'));
$button->press();
$this->waitForText('Dog has been removed.');
// Assert the focus is set back on the open button of the media field.
$this->assertJsCondition('jQuery("#field_twin_media-media-library-wrapper .js-media-library-open-button").is(":focus")');
// Assert we can select the same media item twice.
$this->openMediaLibraryForField('field_twin_media');
$page->checkField('Select Dog');
$this->pressInsertSelected('Added one media item.');
$this->openMediaLibraryForField('field_twin_media');
$page->checkField('Select Dog');
$this->pressInsertSelected('Added one media item.');
$this->waitForElementsCount('css', '.field--name-field-twin-media [data-media-library-item-delta]', 2);
// Assert that we can toggle the visibility of the weight inputs when the
// field contains more than one item.
$wrapper = $assert_session->elementExists('css', '.field--name-field-twin-media');
$wrapper->pressButton('Show media item weights');
// Ensure that the styling doesn't accidentally render the weight field
// unusable.
$assert_session->fieldExists('Weight', $wrapper)->click();
$wrapper->pressButton('Hide media item weights');
// Assert the same has been added twice and remove the items again.
$this->waitForElementsCount('css', '.field--name-field-twin-media [data-media-library-item-delta]', 2);
$assert_session->hiddenFieldValueEquals('field_twin_media[selection][0][target_id]', 4);
$assert_session->hiddenFieldValueEquals('field_twin_media[selection][1][target_id]', 4);
$wrapper->pressButton('Remove');
$this->waitForText('Dog has been removed.');
$wrapper->pressButton('Remove');
$this->waitForText('Dog has been removed.');
$result = $wrapper->waitFor(10, function ($wrapper) {
/** @var \Behat\Mink\Element\NodeElement $wrapper */
return $wrapper->findButton('Remove') == NULL;
});
$this->assertTrue($result);
// Assert the selection is persistent in the media library modal, and
// the number of selected items is displayed correctly.
$this->openMediaLibraryForField('field_twin_media');
// Assert the number of selected items is displayed correctly.
$this->assertSelectedMediaCount('0 of 2 items selected');
// Select a media item, assert the hidden selection field contains the ID of
// the selected item.
$checkboxes = $this->getCheckboxes();
$this->assertCount(4, $checkboxes);
$this->selectMediaItem(0, '1 of 2 items selected');
$assert_session->hiddenFieldValueEquals('media-library-modal-selection', '4');
// Select another item and assert the number of selected items is updated.
$this->selectMediaItem(1, '2 of 2 items selected');
$assert_session->hiddenFieldValueEquals('media-library-modal-selection', '4,3');
// Assert unselected items are disabled when the maximum allowed items are
// selected (cardinality for this field is 2).
$this->assertTrue($checkboxes[2]->hasAttribute('disabled'));
$this->assertTrue($checkboxes[3]->hasAttribute('disabled'));
// Assert the selected items are updated when deselecting an item.
$checkboxes[0]->click();
$this->assertSelectedMediaCount('1 of 2 items selected');
$assert_session->hiddenFieldValueEquals('media-library-modal-selection', '3');
// Assert deselected items are available again.
$this->assertFalse($checkboxes[2]->hasAttribute('disabled'));
$this->assertFalse($checkboxes[3]->hasAttribute('disabled'));
// The selection should be persisted when navigating to other media types in
// the modal.
$this->switchToMediaType('Three');
$this->switchToMediaType('One');
$selected_checkboxes = [];
foreach ($this->getCheckboxes() as $checkbox) {
if ($checkbox->isChecked()) {
$selected_checkboxes[] = $checkbox->getValue();
}
}
$this->assertCount(1, $selected_checkboxes);
$assert_session->hiddenFieldValueEquals('media-library-modal-selection', implode(',', $selected_checkboxes));
$this->assertSelectedMediaCount('1 of 2 items selected');
// Add to selection from another type.
$this->switchToMediaType('Two');
$checkboxes = $this->getCheckboxes();
$this->assertCount(4, $checkboxes);
$this->selectMediaItem(0, '2 of 2 items selected');
$assert_session->hiddenFieldValueEquals('media-library-modal-selection', '3,8');
// Assert unselected items are disabled when the maximum allowed items are
// selected (cardinality for this field is 2).
$this->assertFalse($checkboxes[0]->hasAttribute('disabled'));
$this->assertTrue($checkboxes[1]->hasAttribute('disabled'));
$this->assertTrue($checkboxes[2]->hasAttribute('disabled'));
$this->assertTrue($checkboxes[3]->hasAttribute('disabled'));
// Assert the checkboxes are also disabled on other pages.
$this->switchToMediaType('One');
$this->assertTrue($checkboxes[0]->hasAttribute('disabled'));
$this->assertFalse($checkboxes[1]->hasAttribute('disabled'));
$this->assertTrue($checkboxes[2]->hasAttribute('disabled'));
$this->assertTrue($checkboxes[3]->hasAttribute('disabled'));
// Select the items.
$this->pressInsertSelected('Added 2 media items.');
// Assert the open button is disabled.
$open_button = $this->assertElementExistsAfterWait('css', '.js-media-library-open-button[name^="field_twin_media"]');
$this->assertTrue($open_button->hasAttribute('data-disabled-focus'));
$this->assertTrue($open_button->hasAttribute('disabled'));
$this->assertJsCondition('jQuery("#field_twin_media-media-library-wrapper .js-media-library-open-button").is(":disabled")');
// Ensure that the selection completed successfully.
$assert_session->pageTextNotContains('Add or select media');
$assert_session->elementTextNotContains('css', '#field_twin_media-media-library-wrapper', 'Dog');
$assert_session->elementTextContains('css', '#field_twin_media-media-library-wrapper', 'Cat');
$assert_session->elementTextContains('css', '#field_twin_media-media-library-wrapper', 'Turtle');
$assert_session->elementTextNotContains('css', '#field_twin_media-media-library-wrapper', 'Snake');
// Remove "Cat" (happens to be the first remove button on the page).
$button = $assert_session->buttonExists('Remove', $wrapper);
$this->assertSame('Remove Cat', $button->getAttribute('aria-label'));
$button->press();
$this->waitForText('Cat has been removed.');
// Assert the focus is set to the wrapper of the other selected item.
$this->assertJsCondition('jQuery("#field_twin_media-media-library-wrapper [data-media-library-item-delta]").is(":focus")');
$assert_session->elementTextNotContains('css', '#field_twin_media-media-library-wrapper', 'Cat');
$assert_session->elementTextContains('css', '#field_twin_media-media-library-wrapper', 'Turtle');
// Assert the open button is no longer disabled.
$open_button = $assert_session->elementExists('css', '.js-media-library-open-button[name^="field_twin_media"]');
$this->assertFalse($open_button->hasAttribute('data-disabled-focus'));
$this->assertFalse($open_button->hasAttribute('disabled'));
$this->assertJsCondition('jQuery("#field_twin_media-media-library-wrapper .js-media-library-open-button").is(":not(:disabled)")');
// Open the media library again and select another item.
$this->openMediaLibraryForField('field_twin_media');
$this->selectMediaItem(0);
$this->pressInsertSelected('Added one media item.');
$this->waitForElementTextContains('#field_twin_media-media-library-wrapper', 'Dog');
$assert_session->elementTextNotContains('css', '#field_twin_media-media-library-wrapper', 'Cat');
$assert_session->elementTextContains('css', '#field_twin_media-media-library-wrapper', 'Turtle');
$assert_session->elementTextNotContains('css', '#field_twin_media-media-library-wrapper', 'Snake');
// Assert the open button is disabled.
$this->assertTrue($assert_session->elementExists('css', '.js-media-library-open-button[name^="field_twin_media"]')->hasAttribute('data-disabled-focus'));
$this->assertTrue($assert_session->elementExists('css', '.js-media-library-open-button[name^="field_twin_media"]')->hasAttribute('disabled'));
$this->assertJsCondition('jQuery("#field_twin_media-media-library-wrapper .js-media-library-open-button").is(":disabled")');
// Assert the selection is cleared when the modal is closed.
$this->openMediaLibraryForField('field_unlimited_media');
$checkboxes = $this->getCheckboxes();
$this->assertGreaterThanOrEqual(4, count($checkboxes));
// Nothing is selected yet.
$this->assertFalse($checkboxes[0]->isChecked());
$this->assertFalse($checkboxes[1]->isChecked());
$this->assertFalse($checkboxes[2]->isChecked());
$this->assertFalse($checkboxes[3]->isChecked());
$this->assertSelectedMediaCount('0 items selected');
// Select the first 2 items.
$checkboxes[0]->click();
$this->assertSelectedMediaCount('1 item selected');
$checkboxes[1]->click();
$this->assertSelectedMediaCount('2 items selected');
$this->assertTrue($checkboxes[0]->isChecked());
$this->assertTrue($checkboxes[1]->isChecked());
$this->assertFalse($checkboxes[2]->isChecked());
$this->assertFalse($checkboxes[3]->isChecked());
// Close the dialog, reopen it and assert not is selected again.
$assert_session->elementExists('css', '.ui-dialog-titlebar-close')->click();
$this->openMediaLibraryForField('field_unlimited_media');
$checkboxes = $this->getCheckboxes();
$this->assertGreaterThanOrEqual(4, count($checkboxes));
$this->assertFalse($checkboxes[0]->isChecked());
$this->assertFalse($checkboxes[1]->isChecked());
$this->assertFalse($checkboxes[2]->isChecked());
$this->assertFalse($checkboxes[3]->isChecked());
$assert_session->elementExists('css', '.ui-dialog-titlebar-close')->click();
// Finally, save the form.
$assert_session->elementExists('css', '.js-media-library-widget-toggle-weight')->click();
$this->submitForm([
'title[0][value]' => 'My page',
'field_twin_media[selection][0][weight]' => '3',
], 'Save');
$assert_session->pageTextContains('Basic Page My page has been created');
// We removed this item earlier.
$assert_session->pageTextNotContains('Cat');
// This item was never selected.
$assert_session->pageTextNotContains('Snake');
// "Turtle" should come after "Dog", since we changed the weight.
$assert_session->elementExists('css', '.field--name-field-twin-media > .field__items > .field__item:last-child:contains("Turtle")');
// Make sure everything that was selected shows up.
$assert_session->pageTextContains('Dog');
$assert_session->pageTextContains('Turtle');
// Re-edit the content and make a new selection.
$this->drupalGet('node/1/edit');
$assert_session->pageTextContains('Dog');
$assert_session->pageTextNotContains('Cat');
$assert_session->pageTextNotContains('Bear');
$assert_session->pageTextNotContains('Horse');
$assert_session->pageTextContains('Turtle');
$assert_session->pageTextNotContains('Snake');
$this->openMediaLibraryForField('field_unlimited_media');
// Select all media items of type one (should also contain Dog, again).
$this->selectMediaItem(0);
$this->selectMediaItem(1);
$this->selectMediaItem(2);
$this->selectMediaItem(3);
$this->pressInsertSelected('Added 4 media items.');
$this->waitForText('Dog');
$assert_session->pageTextContains('Cat');
$assert_session->pageTextContains('Bear');
$assert_session->pageTextContains('Horse');
$assert_session->pageTextContains('Turtle');
$assert_session->pageTextNotContains('Snake');
$this->submitForm([], 'Save');
$assert_session->pageTextContains('Dog');
$assert_session->pageTextContains('Cat');
$assert_session->pageTextContains('Bear');
$assert_session->pageTextContains('Horse');
$assert_session->pageTextContains('Turtle');
$assert_session->pageTextNotContains('Snake');
}
/**
* Tests saving a required media library field.
*/
public function testRequiredMediaField(): void {
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
// Make field_unlimited_media required.
$field_config = FieldConfig::loadByName('node', 'basic_page', 'field_unlimited_media');
$field_config->setRequired(TRUE)->save();
$this->drupalGet('node/add/basic_page');
$page->fillField('Title', 'My page');
$page->pressButton('Save');
// Check that a clear error message is shown.
$assert_session->pageTextNotContains('This value should not be null.');
$assert_session->pageTextContains(sprintf('%s field is required.', $field_config->label()));
// Open the media library, select an item and save the node.
$this->openMediaLibraryForField('field_unlimited_media');
$this->selectMediaItem(0);
$this->pressInsertSelected('Added one media item.');
$page->pressButton('Save');
// Confirm that the node was created.
$this->assertSession()->pageTextContains('Basic page My page has been created.');
}
/**
* Tests that changed order is maintained after removing a selection.
*/
public function testRemoveAfterReordering(): void {
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
$this->drupalGet('node/add/basic_page');
$page->fillField('Title', 'My page');
$this->openMediaLibraryForField('field_unlimited_media');
$page->checkField('Select Dog');
$page->checkField('Select Cat');
$page->checkField('Select Bear');
// Order: Dog - Cat - Bear.
$this->pressInsertSelected('Added 3 media items.');
// Move first item (Dog) to the end.
// Order: Cat - Bear - Dog.
$this->sortableAfter('[data-media-library-item-delta="0"]', '[data-media-library-item-delta="2"]', '.js-media-library-selection');
$wrapper = $assert_session->elementExists('css', '.field--name-field-unlimited-media');
// Remove second item (Bear).
// Order: Cat - Dog.
$wrapper->find('css', "[aria-label='Remove Bear']")->press();
$this->waitForText('Bear has been removed.');
$page->pressButton('Save');
$assert_session->elementTextContains('css', '.field--name-field-unlimited-media > .field__items > .field__item:last-child', 'Dog');
}
/**
* Tests that order is correct after re-order and adding another item.
*/
public function testAddAfterReordering(): void {
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
$this->drupalGet('node/add/basic_page');
$page->fillField('Title', 'My page');
$this->openMediaLibraryForField('field_unlimited_media');
$page->checkField('Select Dog');
$page->checkField('Select Cat');
// Order: Dog - Cat.
$this->pressInsertSelected('Added 2 media items.');
// Change positions.
// Order: Cat - Dog.
$this->sortableAfter('[data-media-library-item-delta="0"]', '[data-media-library-item-delta="1"]', '.js-media-library-selection');
$this->openMediaLibraryForField('field_unlimited_media');
$this->selectMediaItem(2);
// Order: Cat - Dog - Bear.
$this->pressInsertSelected('Added one media item.');
$page->pressButton('Save');
$assert_session->elementTextContains('css', '.field--name-field-unlimited-media > .field__items > .field__item:first-child', 'Cat');
$assert_session->elementTextContains('css', '.field--name-field-unlimited-media > .field__items > .field__item:last-child', 'Bear');
}
/**
* Checks for inclusion of text in #drupal-live-announce.
*
* @param string $expected_message
* The text that is expected to be present in the #drupal-live-announce element.
*
* @internal
*/
protected function assertAnnounceContains(string $expected_message): void {
$assert_session = $this->assertSession();
$this->assertNotEmpty($assert_session->waitForElement('css', "#drupal-live-announce:contains('$expected_message')"));
}
/**
* {@inheritdoc}
*/
protected function sortableUpdate($item, $from, $to = NULL) {
// See core/modules/media_library/js/media_library.widget.js.
$script = <<<JS
(function ($) {
var selection = document.querySelectorAll('.js-media-library-selection');
selection.forEach(function (widget) {
$(widget).children().each(function (index, child) {
$(child).find('.js-media-library-item-weight').val(index);
});
});
})(jQuery)
JS;
$this->getSession()->executeScript($script);
}
/**
* Tests the preview displayed by the field widget.
*/
public function testWidgetPreview(): void {
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
$node = $this->drupalCreateNode([
'type' => 'basic_page',
'field_unlimited_media' => [
$this->mediaItems['Horse'],
],
]);
$media_id = $this->mediaItems['Horse']->id();
// Assert that preview is present for current user, who can view media.
$this->drupalGet($node->toUrl('edit-form'));
$assert_session->elementTextContains('css', '[data-drupal-selector="edit-field-unlimited-media-selection-0"]', 'Horse');
$remove_button = $page->find('css', '[data-drupal-selector="edit-field-unlimited-media-selection-0-remove-button"]');
$this->assertSame('Remove Horse', $remove_button->getAttribute('aria-label'));
$assert_session->pageTextNotContains('You do not have permission to view media item');
$remove_button->press();
$this->waitForText("Removing Horse.");
$this->waitForText("Horse has been removed.");
// Logout without saving.
$this->drupalLogout();
// Create a user who can edit content but not view media.
// Must remove permission from authenticated role first, otherwise the new
// user will inherit that permission.
$role = Role::load(RoleInterface::AUTHENTICATED_ID);
$role->revokePermission('view media');
$role->save();
$non_media_editor = $this->drupalCreateUser([
'access content',
'create basic_page content',
'edit any basic_page content',
]);
$this->drupalLogin($non_media_editor);
// Assert that preview does not reveal media name.
$this->drupalGet($node->toUrl('edit-form'));
// There should be no preview name.
$assert_session->elementTextNotContains('css', '[data-drupal-selector="edit-field-unlimited-media-selection-0"]', 'Horse');
// The remove button should have a generic message.
$remove_button = $page->find('css', '[data-drupal-selector="edit-field-unlimited-media-selection-0-remove-button"]');
$this->assertSame('Remove media', $remove_button->getAttribute('aria-label'));
$assert_session->pageTextContains("You do not have permission to view media item $media_id.");
// Confirm ajax text does not reveal media name.
$remove_button->press();
$this->waitForText("Removing media.");
$this->waitForText("Media has been removed.");
}
}

View File

@@ -0,0 +1,117 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media_library\FunctionalJavascript;
// cspell:ignore shatner
/**
* Tests field UI integration for media library widget.
*
* @group media_library
*/
class FieldUiIntegrationTest extends MediaLibraryTestBase {
/**
* {@inheritdoc}
*/
protected $strictConfigSchema = FALSE;
/**
* {@inheritdoc}
*/
protected static $modules = ['field_ui', 'block'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->drupalPlaceBlock('local_tasks_block');
// Create a user who can add media fields.
$user = $this->drupalCreateUser([
'access administration pages',
'administer node fields',
'administer node form display',
]);
$this->drupalLogin($user);
$this->drupalCreateContentType(['type' => 'article']);
$this->drupalCreateContentType(['type' => 'page']);
$this->createMediaItems([
'type_one' => [
'Horse',
'Bear',
'Cat',
'Dog',
],
]);
}
/**
* Tests field UI integration for media library widget.
*/
public function testFieldUiIntegration(): void {
$page = $this->getSession()->getPage();
$assert_session = $this->assertSession();
$user = $this->drupalCreateUser([
'access administration pages',
'administer node fields',
'administer node form display',
'view media',
'bypass node access',
]);
$this->drupalLogin($user);
$this->drupalGet('/admin/structure/types/manage/article/fields/add-field');
$page->find('css', "[name='new_storage_type'][value='field_ui:entity_reference:media']")->getParent()->click();
$page->findButton('Continue')->click();
$this->assertNotNull($assert_session->waitForField('label'));
$page->fillField('label', 'Shatner');
$this->waitForText('field_shatner');
$page->pressButton('Continue');
$this->assertMatchesRegularExpression('/.*article\/add-field\/node\/field_shatner.*/', $this->getUrl());
$assert_session->pageTextNotContains('Undefined index: target_bundles');
$this->waitForFieldExists('Type One')->check();
$this->assertElementExistsAfterWait('css', '[name="settings[handler_settings][target_bundles][type_one]"][checked="checked"]');
$page->checkField('settings[handler_settings][target_bundles][type_two]');
$this->assertElementExistsAfterWait('css', '[name="settings[handler_settings][target_bundles][type_two]"][checked="checked"]');
$page->checkField('settings[handler_settings][target_bundles][type_three]');
$this->assertElementExistsAfterWait('css', '[name="settings[handler_settings][target_bundles][type_three]"][checked="checked"]');
$page->pressButton('Save settings');
$assert_session->pageTextContains('Saved Shatner configuration.');
$this->drupalGet('/admin/structure/types/manage/article/fields/node.article.field_shatner');
$assert_session->checkboxNotChecked('set_default_value');
$page->checkField('set_default_value');
$this->assertElementExistsAfterWait('css', "#field_shatner-media-library-wrapper-default_value_input")
->pressButton('Add media');
$this->waitForText('Add or select media');
$this->selectMediaItem(0);
$this->pressInsertSelected('Added one media item.');
$page->pressButton('Save settings');
$assert_session->pageTextContains('Saved Shatner configuration.');
$this->drupalGet('/admin/structure/types/manage/article/fields/node.article.field_shatner');
$assert_session->checkboxChecked('set_default_value');
// Create a new instance of an existing field storage and assert that it
// automatically uses the media library.
$this->drupalGet('/admin/structure/types/manage/page/fields/reuse');
$this->assertSession()->elementExists('css', "input[value=Re-use][name=field_shatner]");
$this->click("input[value=Re-use][name=field_shatner]");
$this->waitForFieldExists('Type One')->check();
$this->assertElementExistsAfterWait('css', '[name="settings[handler_settings][target_bundles][type_one]"][checked="checked"]');
$page->pressButton('Save settings');
$this->drupalGet('/admin/structure/types/manage/page/form-display');
$assert_session->fieldValueEquals('fields[field_shatner][type]', 'media_library_widget');
}
}

View File

@@ -0,0 +1,491 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media_library\FunctionalJavascript;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
use Drupal\media\Entity\Media;
/**
* Base class for functional tests of Media Library functionality.
*/
abstract class MediaLibraryTestBase extends WebDriverTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['media_library_test', 'hold_test'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Create media items.
*
* @param array $media_items
* A nested array of media item names keyed by media type.
*
* @return \Drupal\media\MediaInterface[]
* An array of media entities keyed by the names passed in.
*/
protected function createMediaItems(array $media_items) {
$created_items = [];
$time = time();
foreach ($media_items as $type => $names) {
foreach ($names as $name) {
/** @var \Drupal\media\MediaInterface $media */
$media = Media::create([
'name' => $name,
'bundle' => $type,
]);
$source_field = $media->getSource()
->getSourceFieldDefinition($media->bundle->entity)
->getName();
$media->set($source_field, $name)->setCreatedTime(++$time)->save();
$created_items[$name] = $media;
}
}
return $created_items;
}
/**
* Asserts that text appears on page after a wait.
*
* @param string $text
* The text that should appear on the page.
* @param int $timeout
* Timeout in milliseconds, defaults to 10000.
*
* @todo replace with whatever gets added in
* https://www.drupal.org/node/3061852
*/
protected function waitForText($text, $timeout = 10000) {
$result = $this->assertSession()->waitForText($text, $timeout);
$this->assertNotEmpty($result, "\"$text\" not found");
}
/**
* Asserts that text does not appear on page after a wait.
*
* @param string $text
* The text that should not be on the page.
* @param int $timeout
* Timeout in milliseconds, defaults to 10000.
*
* @todo replace with whatever gets added in
* https://www.drupal.org/node/3061852
*/
protected function waitForNoText($text, $timeout = 10000) {
$page = $this->getSession()->getPage();
$result = $page->waitFor($timeout / 1000, function ($page) use ($text) {
$actual = preg_replace('/\s+/u', ' ', $page->getText());
$regex = '/' . preg_quote($text, '/') . '/ui';
return (bool) !preg_match($regex, $actual);
});
$this->assertNotEmpty($result, "\"$text\" was found but shouldn't be there.");
}
/**
* Checks for a specified number of specific elements on page after wait.
*
* @param string $selector_type
* Element selector type (css, xpath)
* @param string|array $selector
* Element selector.
* @param int $count
* Expected count.
* @param int $timeout
* Timeout in milliseconds, defaults to 10000.
*
* @todo replace with whatever gets added in
* https://www.drupal.org/node/3061852
*/
protected function waitForElementsCount($selector_type, $selector, $count, $timeout = 10000) {
$page = $this->getSession()->getPage();
$start = microtime(TRUE);
$end = $start + ($timeout / 1000);
do {
$nodes = $page->findAll($selector_type, $selector);
if (count($nodes) === $count) {
return;
}
usleep(100000);
} while (microtime(TRUE) < $end);
$this->assertSession()->elementsCount($selector_type, $selector, $count);
}
/**
* Asserts that text appears in an element after a wait.
*
* @param string $selector
* The CSS selector of the element to check.
* @param string $text
* The text that should appear in the element.
* @param int $timeout
* Timeout in milliseconds, defaults to 10000.
*
* @todo replace with whatever gets added in
* https://www.drupal.org/node/3061852
*/
protected function waitForElementTextContains($selector, $text, $timeout = 10000) {
$element = $this->assertSession()->waitForElement('css', "$selector:contains('$text')", $timeout);
$this->assertNotEmpty($element);
}
/**
* Waits for the specified selector and returns it if not empty.
*
* @param string $selector
* The selector engine name. See ElementInterface::findAll() for the
* supported selectors.
* @param string|array $locator
* The selector locator.
* @param int $timeout
* Timeout in milliseconds, defaults to 10000.
*
* @return \Behat\Mink\Element\NodeElement
* The page element node if found. If not found, the test fails.
*
* @todo replace with whatever gets added in
* https://www.drupal.org/node/3061852
*/
protected function assertElementExistsAfterWait($selector, $locator, $timeout = 10000) {
$element = $this->assertSession()->waitForElement($selector, $locator, $timeout);
$this->assertNotEmpty($element);
return $element;
}
/**
* Gets the menu of available media types.
*
* @return \Behat\Mink\Element\NodeElement
* The menu of available media types.
*/
protected function getTypesMenu() {
return $this->assertSession()
->elementExists('css', '.js-media-library-menu');
}
/**
* Clicks a media type tab and waits for it to appear.
*/
protected function switchToMediaType($type) {
$link = $this->assertSession()
->elementExists('named', ['link', "Type $type"], $this->getTypesMenu());
if ($link->hasClass('active')) {
// There is nothing to do as the type is already active.
return;
}
$link->click();
$result = $link->waitFor(10, function ($link) {
/** @var \Behat\Mink\Element\NodeElement $link */
return $link->hasClass('active');
});
$this->assertNotEmpty($result);
// assertWaitOnAjaxRequest() required for input "id" attributes to
// consistently match their label's "for" attribute.
$this->assertSession()->assertWaitOnAjaxRequest();
}
/**
* Checks for the existence of a field on page after wait.
*
* @param string $field
* The field to find.
* @param int $timeout
* Timeout in milliseconds, defaults to 10000.
*
* @return \Behat\Mink\Element\NodeElement|null
* The element if found, otherwise null.
*
* @todo replace with whatever gets added in
* https://www.drupal.org/node/3061852
*/
protected function waitForFieldExists($field, $timeout = 10000) {
$assert_session = $this->assertSession();
$assert_session->waitForField($field, $timeout);
return $assert_session->fieldExists($field);
}
/**
* Waits for a file field to exist before uploading.
*/
public function addMediaFileToField($locator, $path) {
$page = $this->getSession()->getPage();
$this->waitForFieldExists($locator);
$page->attachFileToField($locator, $path);
}
/**
* Clicks "Save and select||insert" button and waits for AJAX completion.
*
* @param string $operation
* The final word of the button to be clicked.
*/
protected function saveAnd($operation) {
$this->assertElementExistsAfterWait('css', '.ui-dialog-buttonpane')->pressButton("Save and $operation");
// assertWaitOnAjaxRequest() required for input "id" attributes to
// consistently match their label's "for" attribute.
$this->assertSession()->assertWaitOnAjaxRequest();
}
/**
* Clicks "Save" button and waits for AJAX completion.
*
* @param bool $expect_errors
* Whether validation errors are expected after the "Save" button is
* pressed. Defaults to FALSE.
*/
protected function pressSaveButton($expect_errors = FALSE) {
$buttons = $this->assertElementExistsAfterWait('css', '.ui-dialog-buttonpane');
$buttons->pressButton('Save');
// If no validation errors are expected, wait for the "Insert selected"
// button to return.
if (!$expect_errors) {
$result = $buttons->waitFor(10, function ($buttons) {
/** @var \Behat\Mink\Element\NodeElement $buttons */
return $buttons->findButton('Insert selected');
});
$this->assertNotEmpty($result);
}
// assertWaitOnAjaxRequest() required for input "id" attributes to
// consistently match their label's "for" attribute.
$this->assertSession()->assertWaitOnAjaxRequest();
}
/**
* Clicks a button that opens a media widget and confirms it is open.
*
* @param string $field_name
* The machine name of the field for which to open the media library.
* @param string $after_open_selector
* The selector to look for after the button is clicked.
*
* @return \Behat\Mink\Element\NodeElement
* The NodeElement found via $after_open_selector.
*/
protected function openMediaLibraryForField($field_name, $after_open_selector = '.js-media-library-menu') {
$this->assertElementExistsAfterWait('css', "#$field_name-media-library-wrapper.js-media-library-widget")
->pressButton('Add media');
$this->waitForText('Add or select media');
// Assert that the grid display is visible and the links to toggle between
// the grid and table displays are present.
$this->assertMediaLibraryGrid();
$assert_session = $this->assertSession();
$assert_session->linkExists('Grid');
$assert_session->linkExists('Table');
// The "select all" checkbox should never be present in the modal.
$assert_session->elementNotExists('css', '.media-library-select-all');
return $this->assertElementExistsAfterWait('css', $after_open_selector);
}
/**
* Gets the "Additional selected media" area after adding new media.
*
* @param bool $open
* Whether or not to open the area before returning it. Defaults to TRUE.
*
* @return \Behat\Mink\Element\NodeElement
* The "additional selected media" area.
*/
protected function getSelectionArea($open = TRUE) {
$summary = $this->assertElementExistsAfterWait('css', 'summary:contains("Additional selected media")');
if ($open) {
$summary->click();
}
return $summary->getParent();
}
/**
* Asserts a media item was added, but not yet saved.
*
* @param int $index
* (optional) The index of the media item, if multiple items can be added at
* once. Defaults to 0.
*/
protected function assertMediaAdded($index = 0) {
$selector = '.js-media-library-add-form-added-media';
// Assert that focus is shifted to the new media items.
$this->assertJsCondition('jQuery("' . $selector . '").is(":focus")');
$assert_session = $this->assertSession();
$assert_session->pageTextMatches('/The media items? ha(s|ve) been created but ha(s|ve) not yet been saved. Fill in any required fields and save to add (it|them) to the media library./');
$assert_session->elementAttributeContains('css', $selector, 'aria-label', 'Added media items');
$fields = $this->assertElementExistsAfterWait('css', '[data-drupal-selector="edit-media-' . $index . '-fields"]');
$assert_session->elementNotExists('css', '.js-media-library-menu');
// Assert extraneous components were removed in
// FileUploadForm::hideExtraSourceFieldComponents().
$assert_session->elementNotExists('css', '[data-drupal-selector$="preview"]', $fields);
$assert_session->buttonNotExists('Remove', $fields);
$assert_session->elementNotExists('css', '[data-drupal-selector$="filename"]', $fields);
}
/**
* Asserts that media was not added, i.e. due to a validation error.
*/
protected function assertNoMediaAdded() {
// Assert the focus is shifted to the first tabbable element of the add
// form, which should be the source field.
$this->assertJsCondition('jQuery(tabbable.tabbable(document.getElementById("media-library-add-form-wrapper"))[0]).is(":focus")');
$this->assertSession()
->elementNotExists('css', '[data-drupal-selector="edit-media-0-fields"]');
$this->getTypesMenu();
}
/**
* Presses the modal's "Insert selected" button.
*
* @param string $expected_announcement
* (optional) The expected screen reader announcement once the modal is
* closed.
* @param bool $should_close
* (optional) TRUE if we expect the modal to be successfully closed.
*
* @todo Consider requiring screen reader assertion every time "Insert
* selected" is pressed in
* https://www.drupal.org/project/drupal/issues/3087227.
*/
protected function pressInsertSelected($expected_announcement = NULL, bool $should_close = TRUE) {
$this->assertSession()
->elementExists('css', '.ui-dialog-buttonpane')
->pressButton('Insert selected');
if ($should_close) {
$this->waitForNoText('Add or select media');
}
if ($expected_announcement) {
$this->waitForText($expected_announcement);
}
}
/**
* Gets all available media item checkboxes.
*
* @return \Behat\Mink\Element\NodeElement[]
* The available checkboxes.
*/
protected function getCheckboxes() {
return $this->getSession()
->getPage()
->findAll('css', '.js-media-library-view .js-click-to-select-checkbox input');
}
/**
* Selects an item in the media library modal.
*
* @param int $index
* The zero-based index of the item to select.
* @param string $expected_selected_count
* (optional) The expected text of the selection counter.
*/
protected function selectMediaItem($index, $expected_selected_count = NULL) {
$checkboxes = $this->getCheckboxes();
$this->assertGreaterThan($index, count($checkboxes));
$checkboxes[$index]->check();
if ($expected_selected_count) {
$this->assertSelectedMediaCount($expected_selected_count);
}
}
/**
* De-selects an item in the media library modal.
*
* @param int $index
* The zero-based index of the item to unselect.
*/
protected function deselectMediaItem(int $index): void {
$checkboxes = $this->getCheckboxes();
$this->assertGreaterThan($index, count($checkboxes));
$checkboxes[$index]->uncheck();
}
/**
* Switches to the grid display of the widget view.
*/
protected function switchToMediaLibraryGrid() {
$this->getSession()->getPage()->clickLink('Grid');
// Assert the display change is correctly announced for screen readers.
$this->assertAnnounceContains('Loading grid view.');
$this->assertAnnounceContains('Changed to grid view.');
$this->assertMediaLibraryGrid();
}
/**
* Switches to the table display of the widget view.
*/
protected function switchToMediaLibraryTable() {
hold_test_response(TRUE);
$this->getSession()->getPage()->clickLink('Table');
// Assert the display change is correctly announced for screen readers.
$this->assertAnnounceContains('Loading table view.');
hold_test_response(FALSE);
$this->assertAnnounceContains('Changed to table view.');
$this->assertMediaLibraryTable();
}
/**
* Asserts that the grid display of the widget view is visible.
*/
protected function assertMediaLibraryGrid() {
$this->assertSession()->assertWaitOnAjaxRequest();
$this->assertSession()
->elementExists('css', '.js-media-library-view[data-view-display-id="widget"]');
}
/**
* Asserts that the table display of the widget view is visible.
*/
protected function assertMediaLibraryTable() {
$this->assertSession()
->elementExists('css', '.js-media-library-view[data-view-display-id="widget_table"]');
}
/**
* Asserts the current text of the selected item counter.
*
* @param string $text
* The expected text of the counter.
*/
protected function assertSelectedMediaCount($text) {
$selected_count = $this->assertSession()
->elementExists('css', '.js-media-library-selected-count');
$this->assertSame('status', $selected_count->getAttribute('role'));
$this->assertSame('polite', $selected_count->getAttribute('aria-live'));
$this->assertSame('true', $selected_count->getAttribute('aria-atomic'));
$this->assertSame($text, $selected_count->getText());
}
/**
* Checks for inclusion of text in #drupal-live-announce.
*
* @param string $expected_message
* The text expected to be present in #drupal-live-announce.
*
* @internal
*/
protected function assertAnnounceContains(string $expected_message): void {
$assert_session = $this->assertSession();
$this->assertNotEmpty($assert_session->waitForElement('css', "#drupal-live-announce:contains('$expected_message')"));
}
}

View File

@@ -0,0 +1,154 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media_library\FunctionalJavascript;
/**
* Tests the grid-style media overview page.
*
* @group media_library
*/
class MediaOverviewTest extends MediaLibraryTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['block'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Create a few example media items for use in selection.
$this->createMediaItems([
'type_one' => [
'Horse',
'Bear',
'Cat',
'Dog',
],
'type_two' => [
'Crocodile',
'Lizard',
'Snake',
'Turtle',
],
]);
$this->drupalPlaceBlock('local_tasks_block');
$this->drupalPlaceBlock('local_actions_block');
$user = $this->drupalCreateUser([
'access media overview',
'create media',
'delete any media',
'update any media',
]);
$this->drupalLogin($user);
}
/**
* Tests that the Media Library's administration page works as expected.
*/
public function testAdministrationPage(): void {
$session = $this->getSession();
$page = $session->getPage();
$assert_session = $this->assertSession();
// Visit the administration page.
$this->drupalGet('admin/content/media');
// There should be links to both the grid and table displays.
$assert_session->linkExists('Grid');
$assert_session->linkExists('Table');
// We should see the table view and a link to add media.
$assert_session->elementExists('css', '[data-drupal-selector="views-form-media-media-page-list"] table');
$assert_session->linkExists('Add media');
// Go to the grid display for the rest of the test.
$page->clickLink('Grid');
$assert_session->addressEquals('admin/content/media-grid');
// Verify that the "Add media" link is present.
$assert_session->linkExists('Add media');
// Verify that media from two separate types is present.
$assert_session->pageTextContains('Dog');
$assert_session->pageTextContains('Turtle');
// Verify that the media name does not contain a link. The selector is
// tricky, so start by asserting ".js-media-library-item-preview + div"
// can select a div containing a media name.
$assert_session->elementExists('css', '.js-media-library-item-preview + div:contains("Dog")');
$assert_session->elementExists('css', '.js-media-library-item-preview + div:contains("Turtle")');
$assert_session->elementNotExists('css', '.js-media-library-item-preview + div a');
// Verify that there are links to edit and delete media items.
$assert_session->linkExists('Edit Dog');
$assert_session->linkExists('Delete Turtle');
// Test that users can filter by type.
$page->selectFieldOption('Media type', 'Type One');
$page->pressButton('Apply filters');
$this->waitForNoText('Turtle');
$assert_session->pageTextContains('Dog');
$page->selectFieldOption('Media type', 'Type Two');
$page->pressButton('Apply filters');
$this->waitForText('Turtle');
$assert_session->pageTextNotContains('Dog');
// Test that selecting elements as a part of bulk operations works.
$page->selectFieldOption('Media type', '- Any -');
$assert_session->elementExists('css', '#views-exposed-form-media-library-page')->submit();
$this->waitForText('Dog');
// Select the "Delete media" action.
$page->selectFieldOption('Action', 'Delete media');
$this->waitForText('Dog');
// This tests that anchor tags clicked inside the preview are suppressed.
$this->getSession()->executeScript('jQuery(".js-click-to-select-trigger a")[4].click()');
$this->submitForm([], 'Apply to selected items');
$assert_session->pageTextContains('Dog');
$assert_session->pageTextNotContains('Cat');
// For reasons that are not clear, deleting media items by pressing the
// "Delete" button can fail (the button is found, but never actually pressed
// by the Mink driver). This workaround allows the delete form to be
// submitted.
$assert_session->elementExists('css', 'form')->submit();
$assert_session->pageTextNotContains('Dog');
$assert_session->pageTextContains('Cat');
// Test the 'Select all media' checkbox and assert that it makes the
// expected announcements.
$select_all = $this->waitForFieldExists('Select all media');
$select_all->check();
$this->waitForText('All 7 items selected');
$select_all->uncheck();
$this->waitForText('Zero items selected');
$select_all->check();
$page->selectFieldOption('Action', 'media_delete_action');
$this->submitForm([], 'Apply to selected items');
// For reasons that are not clear, deleting media items by pressing the
// "Delete" button can fail (the button is found, but never actually pressed
// by the Mink driver). This workaround allows the delete form to be
// submitted.
$assert_session->elementExists('css', 'form')->submit();
$assert_session->pageTextNotContains('Cat');
$assert_session->pageTextNotContains('Turtle');
$assert_session->pageTextNotContains('Snake');
// Test empty text.
$assert_session->pageTextContains('No media available.');
}
}

View File

@@ -0,0 +1,181 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media_library\FunctionalJavascript;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\file\Entity\File;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\media\Entity\Media;
use Drupal\Tests\field\Traits\EntityReferenceFieldCreationTrait;
use Drupal\Tests\media\Traits\MediaTypeCreationTrait;
use Drupal\Tests\TestFileCreationTrait;
/**
* Tests media library for translatable media.
*
* @group media_library
*/
class TranslationsTest extends WebDriverTestBase {
use EntityReferenceFieldCreationTrait;
use MediaTypeCreationTrait;
use TestFileCreationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'content_translation',
'field',
'media',
'media_library',
'node',
'views',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Create some languages.
foreach (['nl', 'es'] as $langcode) {
ConfigurableLanguage::createFromLangcode($langcode)->save();
}
// Create an image media type and article node type.
$this->createMediaType('image', ['id' => 'image']);
$this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
// Make the media translatable and ensure the change is picked up.
\Drupal::service('content_translation.manager')->setEnabled('media', 'image', TRUE);
// Create a media reference field on articles.
$this->createEntityReferenceField(
'node',
'article',
'field_media',
'Media',
'media',
'default',
['target_bundles' => ['image']],
FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED
);
// Add the media field to the form display.
\Drupal::service('entity_display.repository')->getFormDisplay('node', 'article')
->setComponent('field_media', ['type' => 'media_library_widget'])
->save();
// Create a file to user for our images.
$image = File::create([
'uri' => $this->getTestFiles('image')[0]->uri,
]);
$image->setPermanent();
$image->save();
// Create a translated and untranslated media item in each language.
// cSpell:disable
$media_items = [
['nl' => 'Eekhoorn', 'es' => 'Ardilla'],
['es' => 'Zorro', 'nl' => 'Vos'],
['nl' => 'Hert'],
['es' => 'Tejón'],
];
// cSpell:enable
foreach ($media_items as $translations) {
$default_langcode = key($translations);
$default_name = array_shift($translations);
$media = Media::create([
'name' => $default_name,
'bundle' => 'image',
'field_media_image' => $image,
'langcode' => $default_langcode,
]);
foreach ($translations as $langcode => $name) {
$media->addTranslation($langcode, ['name' => $name]);
}
$media->save();
}
$user = $this->drupalCreateUser([
'access administration pages',
'access content',
'access media overview',
'edit own article content',
'create article content',
'administer media',
]);
$this->drupalLogin($user);
}
/**
* Tests the media library widget shows all media only once.
*/
public function testMediaLibraryTranslations(): void {
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
// All translations should be shown in the administration overview,
// regardless of the interface language.
$this->drupalGet('nl/admin/content/media-grid');
$assert_session->elementsCount('css', '.js-media-library-item', 6);
$media_items = $page->findAll('css', '.js-media-library-item-preview + div');
$media_names = [];
foreach ($media_items as $media_item) {
$media_names[] = $media_item->getText();
}
sort($media_names);
// cSpell:disable-next-line
$this->assertSame(['Ardilla', 'Eekhoorn', 'Hert', 'Tejón', 'Vos', 'Zorro'], $media_names);
$this->drupalGet('es/admin/content/media-grid');
$assert_session->elementsCount('css', '.js-media-library-item', 6);
$media_items = $page->findAll('css', '.js-media-library-item-preview + div');
$media_names = [];
foreach ($media_items as $media_item) {
$media_names[] = $media_item->getText();
}
sort($media_names);
// cSpell:disable-next-line
$this->assertSame(['Ardilla', 'Eekhoorn', 'Hert', 'Tejón', 'Vos', 'Zorro'], $media_names);
// All media should only be shown once, and should be shown in the interface
// language.
$this->drupalGet('nl/node/add/article');
$assert_session->elementExists('css', '.js-media-library-open-button[name^="field_media"]')->click();
$assert_session->waitForText('Add or select media');
$assert_session->elementsCount('css', '.js-media-library-item', 4);
$media_items = $page->findAll('css', '.js-media-library-item-preview + div');
$media_names = [];
foreach ($media_items as $media_item) {
$media_names[] = $media_item->getText();
}
sort($media_names);
// cSpell:disable-next-line
$this->assertSame(['Eekhoorn', 'Hert', 'Tejón', 'Vos'], $media_names);
$this->drupalGet('es/node/add/article');
$assert_session->elementExists('css', '.js-media-library-open-button[name^="field_media"]')->click();
$assert_session->waitForText('Add or select media');
$assert_session->elementsCount('css', '.js-media-library-item', 4);
$media_items = $page->findAll('css', '.js-media-library-item-preview + div');
$media_names = [];
foreach ($media_items as $media_item) {
$media_names[] = $media_item->getText();
}
sort($media_names);
// cSpell:disable-next-line
$this->assertSame(['Ardilla', 'Hert', 'Tejón', 'Zorro'], $media_names);
}
}

View File

@@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media_library\FunctionalJavascript;
/**
* Tests Media Library's integration with Views UI.
*
* @group media_library
*/
class ViewsUiIntegrationTest extends MediaLibraryTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['views_ui'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Create a few example media items for use in selection.
$this->createMediaItems([
'type_one' => [
'Horse',
'Bear',
'Cat',
'Dog',
],
'type_two' => [
'Crocodile',
'Lizard',
'Snake',
'Turtle',
],
]);
$account = $this->drupalCreateUser(['administer views']);
$this->drupalLogin($account);
}
/**
* Tests that the integration with Views works correctly.
*/
public function testViewsAdmin(): void {
$page = $this->getSession()->getPage();
// Assert that the widget can be seen and that there are 8 items.
$this->drupalGet('/admin/structure/views/view/media_library/edit/widget');
$this->waitForElementsCount('css', '.js-media-library-item', 8);
// Assert that filtering works in live preview.
$page->find('css', '.js-media-library-view')->fillField('name', 'snake');
$page->find('css', '.js-media-library-view')->pressButton('Apply filters');
$this->waitForElementsCount('css', '.js-media-library-item', 1);
// Test the same routine but in the view for the table widget.
$this->drupalGet('/admin/structure/views/view/media_library/edit/widget_table');
$this->waitForElementsCount('css', '.js-media-library-item', 8);
// Assert that filtering works in live preview.
$page->find('css', '.js-media-library-view')->fillField('name', 'snake');
$page->find('css', '.js-media-library-view')->pressButton('Apply filters');
$this->waitForElementsCount('css', '.js-media-library-item', 1);
// We cannot test clicking the 'Insert selected' button in either view
// because we expect an AJAX error, which would always throw an exception
// on ::tearDown even if we try to catch it here. If there is an API for
// marking certain elements 'unsuitable for previewing', we could test that
// here.
// @see https://www.drupal.org/project/drupal/issues/3060852
}
}

View File

@@ -0,0 +1,175 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media_library\FunctionalJavascript;
use Drupal\field\Entity\FieldConfig;
use Drupal\media\Entity\Media;
use Drupal\media_library\MediaLibraryState;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
/**
* Tests the media library UI access.
*
* @group media_library
*/
class WidgetAccessTest extends MediaLibraryTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests that the widget access works as expected.
*/
public function testWidgetAccess(): void {
$assert_session = $this->assertSession();
$session = $this->getSession();
$this->createMediaItems([
'type_one' => ['Horse', 'Bear'],
]);
$account = $this->drupalCreateUser(['create basic_page content']);
$this->drupalLogin($account);
// Assert users can not select media items they do not have access to.
$unpublished_media = Media::create([
'name' => 'Mosquito',
'bundle' => 'type_one',
'field_media_test' => 'Mosquito',
'status' => FALSE,
]);
$unpublished_media->save();
// Visit a node create page.
$this->drupalGet('node/add/basic_page');
// Set the hidden value and trigger the mousedown event on the button via
// JavaScript since the field and button are hidden.
$session->executeScript("jQuery('[data-media-library-widget-value=\"field_unlimited_media\"]').val('1,2,{$unpublished_media->id()}')");
$session->executeScript("jQuery('[data-media-library-widget-update=\"field_unlimited_media\"]').trigger('mousedown')");
$this->assertElementExistsAfterWait('css', '.js-media-library-item');
// Assert the published items are selected and the unpublished item is not
// selected.
$assert_session->pageTextContains('Horse');
$assert_session->pageTextContains('Bear');
$assert_session->pageTextNotContains('Mosquito');
$this->drupalLogout();
$role = Role::load(RoleInterface::ANONYMOUS_ID);
$role->revokePermission('view media');
$role->save();
// Create a working state.
$allowed_types = ['type_one', 'type_two', 'type_three', 'type_four'];
// The opener parameters are not relevant to the test, but the opener
// expects them to be there or it will deny access.
$state = MediaLibraryState::create('media_library.opener.field_widget', $allowed_types, 'type_three', 2, [
'entity_type_id' => 'node',
'bundle' => 'basic_page',
'field_name' => 'field_unlimited_media',
]);
$url_options = ['query' => $state->all()];
// Verify that unprivileged users can't access the widget view.
$this->drupalGet('admin/content/media-widget', $url_options);
$assert_session->responseContains('Access denied');
$this->drupalGet('admin/content/media-widget-table', $url_options);
$assert_session->responseContains('Access denied');
$this->drupalGet('media-library', $url_options);
$assert_session->responseContains('Access denied');
// Allow users with 'view media' permission to access the media library view
// and controller. Since we are using the node entity type in the state
// object, ensure the user also has permission to work with those.
$this->grantPermissions($role, [
'create basic_page content',
'view media',
]);
$this->drupalGet('admin/content/media-widget', $url_options);
$assert_session->elementExists('css', '.js-media-library-view');
$this->drupalGet('admin/content/media-widget-table', $url_options);
$assert_session->elementExists('css', '.js-media-library-view');
$this->drupalGet('media-library', $url_options);
$assert_session->elementExists('css', '.js-media-library-view');
// Assert the user does not have access to the media add form if the user
// does not have the 'create media' permission.
$assert_session->fieldNotExists('files[upload][]');
// Assert users can not access the widget displays of the media library view
// without a valid media library state.
$this->drupalGet('admin/content/media-widget');
$assert_session->responseContains('Access denied');
$this->drupalGet('admin/content/media-widget-table');
$assert_session->responseContains('Access denied');
$this->drupalGet('media-library');
$assert_session->responseContains('Access denied');
// Assert users with the 'create media' permission can access the media add
// form.
$this->grantPermissions($role, [
'create media',
]);
$this->drupalGet('media-library', $url_options);
$assert_session->elementExists('css', '.js-media-library-view');
$assert_session->fieldExists('Add files');
// Assert the media library can not be accessed if the required state
// parameters are changed without changing the hash.
$this->drupalGet('media-library', [
'query' => array_merge($url_options['query'], ['media_library_opener_id' => 'fail']),
]);
$assert_session->responseContains('Access denied');
$this->drupalGet('media-library', [
'query' => array_merge($url_options['query'], ['media_library_allowed_types' => ['type_one', 'type_two']]),
]);
$assert_session->responseContains('Access denied');
$this->drupalGet('media-library', [
'query' => array_merge($url_options['query'], ['media_library_selected_type' => 'type_one']),
]);
$assert_session->responseContains('Access denied');
$this->drupalGet('media-library', [
'query' => array_merge($url_options['query'], ['media_library_remaining' => 3]),
]);
$assert_session->responseContains('Access denied');
$this->drupalGet('media-library', [
'query' => array_merge($url_options['query'], ['hash' => 'fail']),
]);
$assert_session->responseContains('Access denied');
}
/**
* Tests the widget with a required field that the user can't access.
*/
public function testRequiredFieldNoAccess(): void {
// Make field_single_media_type required.
$fieldConfig = FieldConfig::loadByName('node', 'basic_page', 'field_single_media_type');
assert($fieldConfig instanceof FieldConfig);
$fieldConfig->setRequired(TRUE)
->save();
// Deny access to the field.
\Drupal::state()->set('media_library_test_entity_field_access_deny_fields', ['field_single_media_type']);
$user = $this->drupalCreateUser([
'access administration pages',
'access content',
'create basic_page content',
'create type_one media',
'view media',
]);
$this->drupalLogin($user);
$this->drupalGet('node/add/basic_page');
$this->assertSession()->elementNotExists('css', '.field--name-field-single-media-type');
$this->submitForm([
'title[0][value]' => $this->randomMachineName(),
], 'Save');
$this->assertSession()->elementNotExists('css', '.messages--error');
$this->assertSession()->pageTextNotContains('Single media type field is required.');
}
}

View File

@@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media_library\FunctionalJavascript;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
/**
* Tests that the widget works as expected for anonymous users.
*
* @group media_library
*/
class WidgetAnonymousTest extends MediaLibraryTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Create a few example media items for use in selection.
$this->createMediaItems([
'type_one' => [
'Cat',
'Dog',
],
]);
// Allow the anonymous user to create pages and view media.
$role = Role::load(RoleInterface::ANONYMOUS_ID);
$this->grantPermissions($role, [
'access content',
'create basic_page content',
'view media',
]);
}
/**
* Tests that the widget works as expected for anonymous users.
*/
public function testWidgetAnonymous(): void {
$assert_session = $this->assertSession();
// Allow the anonymous user to create pages and view media.
$role = Role::load(RoleInterface::ANONYMOUS_ID);
$this->grantPermissions($role, [
'access content',
'create basic_page content',
'view media',
]);
// Ensure the widget works as an anonymous user.
$this->drupalGet('node/add/basic_page');
// Add to the unlimited cardinality field.
$this->openMediaLibraryForField('field_unlimited_media');
// Select the first media item (should be Dog).
$this->selectMediaItem(0);
$this->pressInsertSelected('Added one media item.');
// Ensure that the selection completed successfully.
$this->waitForText('Dog');
$assert_session->fieldNotExists('Weight');
// Add to the unlimited cardinality field.
$this->openMediaLibraryForField('field_unlimited_media');
// Select the second media item (should be Cat).
$this->selectMediaItem(1);
$this->pressInsertSelected('Added one media item.');
// Ensure that the selection completed successfully.
$this->waitForText('Cat');
// Save the form.
$assert_session->elementExists('css', '.js-media-library-widget-toggle-weight')->click();
$this->submitForm([
'title[0][value]' => 'My page',
'field_unlimited_media[selection][0][weight]' => '0',
'field_unlimited_media[selection][1][weight]' => '1',
], 'Save');
$assert_session->pageTextContains('Basic Page My page has been created');
$assert_session->pageTextContains('Dog');
}
}

View File

@@ -0,0 +1,440 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media_library\FunctionalJavascript;
use Drupal\media\Entity\Media;
use Drupal\media_test_oembed\Controller\ResourceController;
use Drupal\Tests\media\Traits\OEmbedTestTrait;
// cspell:ignore Drupalin Hustlin Schipulcon
/**
* Tests that oEmbed media can be added in the Media library's widget.
*
* @group media_library
*/
class WidgetOEmbedTest extends MediaLibraryTestBase {
use OEmbedTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['media_test_oembed'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->lockHttpClientToFixtures();
$this->hijackProviderEndpoints();
// Create a user who can use the Media library.
$user = $this->drupalCreateUser([
'access content',
'create basic_page content',
'view media',
'create media',
'administer media',
]);
$this->drupalLogin($user);
}
/**
* Tests that oEmbed media can be added in the Media library's widget.
*/
public function testWidgetOEmbed(): void {
$this->hijackProviderEndpoints();
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
$youtube_title = "Everyday I'm Drupalin' Drupal Rap (Rick Ross - Hustlin)";
$youtube_url = 'https://www.youtube.com/watch?v=PWjcqE3QKBg';
$vimeo_title = "Drupal Rap Video - Schipulcon09";
$vimeo_url = 'https://vimeo.com/7073899';
ResourceController::setResourceUrl($youtube_url, $this->getFixturesDirectory() . '/video_youtube.json');
ResourceController::setResourceUrl($vimeo_url, $this->getFixturesDirectory() . '/video_vimeo.json');
ResourceController::setResource404('https://www.youtube.com/watch?v=PWjcqE3QKBg1');
// Visit a node create page.
$this->drupalGet('node/add/basic_page');
// Add to the unlimited media field.
$this->openMediaLibraryForField('field_unlimited_media');
// Assert the default tab for media type one does not have an oEmbed form.
$assert_session->fieldNotExists('Add Type Five via URL');
// Assert other media types don't have the oEmbed form fields.
$this->switchToMediaType('Three');
$assert_session->fieldNotExists('Add Type Five via URL');
// Assert we can add an oEmbed video to media type five.
$this->switchToMediaType('Five');
$page->fillField('Add Type Five via URL', $youtube_url);
$assert_session->pageTextContains('Allowed providers: YouTube, Vimeo.');
$page->pressButton('Add');
// assertWaitOnAjaxRequest() required for input "id" attributes to
// consistently match their label's "for" attribute.
$assert_session->assertWaitOnAjaxRequest();
$this->waitForText('The media item has been created but has not yet been saved.');
// There is no other selected media and this is not the advanced ui.
// Assert that the Additional selected media element does not appear.
$assert_session->pageTextNotContains('Additional selected media');
$assert_session->elementNotExists('css', '[data-drupal-selector="edit-selection"]');
// Assert the name field contains the remote video title.
$assert_session->fieldValueEquals('Name', $youtube_title);
$this->pressSaveButton();
$this->waitForText('Add Type Five via URL');
// Load the created media item.
$media_items = Media::loadMultiple();
$added_media = array_pop($media_items);
$added_media_id = $added_media->id();
// Ensure the media item was saved to the library and automatically
// selected. The added media items should be in the first position of the
// add form.
$assert_session->pageTextContains('Add or select media');
$assert_session->pageTextContains($youtube_title);
$assert_session->fieldValueEquals("media_library_select_form[$added_media_id]", $added_media_id);
$assert_session->checkboxChecked("media_library_select_form[$added_media_id]");
// Assert the created oEmbed video is correctly added to the widget.
$this->pressInsertSelected('Added one media item.');
$this->waitForText($youtube_title);
// Open the media library again for the unlimited field and go to the tab
// for media type five.
$this->openMediaLibraryForField('field_unlimited_media');
$this->switchToMediaType('Five');
// Assert the video is available on the tab.
$assert_session->pageTextContains($youtube_title);
// Assert we can only add supported URLs.
$page->fillField('Add Type Five via URL', 'https://www.youtube.com/');
$page->pressButton('Add');
// assertWaitOnAjaxRequest() required for input "id" attributes to
// consistently match their label's "for" attribute.
$assert_session->assertWaitOnAjaxRequest();
$this->waitForText('No matching provider found.');
// Assert we can not add a video ID that doesn't exist. We need to use a
// video ID that will not be filtered by the regex, because otherwise the
// message 'No matching provider found.' will be returned.
$page->fillField('Add Type Five via URL', 'https://www.youtube.com/watch?v=PWjcqE3QKBg1');
$page->pressButton('Add');
// assertWaitOnAjaxRequest() required for input "id" attributes to
// consistently match their label's "for" attribute.
$assert_session->assertWaitOnAjaxRequest();
$this->waitForText('Could not retrieve the oEmbed resource.');
// Select a media item to check if the selection is persisted when adding
// new items.
$checkbox = $page->findField("Select $youtube_title");
$selected_item_id = $checkbox->getAttribute('value');
$checkbox->click();
$assert_session->pageTextContains('1 item selected');
$assert_session->hiddenFieldValueEquals('current_selection', $selected_item_id);
// Assert we can add a oEmbed video with a custom name.
$page->fillField('Add Type Five via URL', $youtube_url);
$page->pressButton('Add');
// assertWaitOnAjaxRequest() required for input "id" attributes to
// consistently match their label's "for" attribute.
$assert_session->assertWaitOnAjaxRequest();
$this->waitForText('The media item has been created but has not yet been saved.');
$page->fillField('Name', 'Custom video title');
// The non-advanced ui should not show the Additional selected media.
$assert_session->pageTextNotContains('Additional selected media');
$assert_session->elementNotExists('css', '[data-drupal-selector="edit-selection"]');
$this->pressSaveButton();
// Load the created media item.
$media_items = Media::loadMultiple();
$added_media = array_pop($media_items);
$added_media_id = $added_media->id();
// Ensure the media item was saved to the library and automatically
// selected. The added media items should be in the first position of the
// add form.
$assert_session->pageTextContains('Add or select media');
$assert_session->pageTextContains('Custom video title');
$assert_session->fieldValueEquals("media_library_select_form[$added_media_id]", $added_media_id);
$assert_session->checkboxChecked("media_library_select_form[$added_media_id]");
// Assert the item that was selected before uploading the file is still
// selected.
$assert_session->pageTextContains('2 items selected');
$assert_session->checkboxChecked("Select Custom video title");
$assert_session->checkboxChecked("Select $youtube_title");
$assert_session->hiddenFieldValueEquals('current_selection', implode(',', [$selected_item_id, $added_media_id]));
$selected_checkboxes = [];
foreach ($this->getCheckboxes() as $checkbox) {
if ($checkbox->isChecked()) {
$selected_checkboxes[] = $checkbox->getAttribute('value');
}
}
$this->assertCount(2, $selected_checkboxes);
// Ensure the created item is added in the widget.
$this->pressInsertSelected('Added 2 media items.');
$this->waitForText('Custom video title');
// Assert we can directly insert added oEmbed media in the widget.
$this->openMediaLibraryForField('field_unlimited_media');
$this->switchToMediaType('Five');
$page->fillField('Add Type Five via URL', $vimeo_url);
$page->pressButton('Add');
$this->waitForText('The media item has been created but has not yet been saved.');
$this->pressSaveButton();
$this->waitForText('Add or select media');
$this->pressInsertSelected();
$this->waitForText($vimeo_title);
// Assert we can remove selected items from the selection area in the oEmbed
// form.
$this->openMediaLibraryForField('field_unlimited_media');
$this->switchToMediaType('Five');
$checkbox = $page->findField("Select $vimeo_title");
$selected_item_id = $checkbox->getAttribute('value');
$checkbox->click();
$assert_session->hiddenFieldValueEquals('current_selection', $selected_item_id);
$page->fillField('Add Type Five via URL', $youtube_url);
$page->pressButton('Add');
// assertWaitOnAjaxRequest() required for input "id" attributes to
// consistently match their label's "for" attribute.
$assert_session->assertWaitOnAjaxRequest();
$this->waitForText('The media item has been created but has not yet been saved');
$page->fillField('Name', 'Another video');
$this->pressSaveButton();
$page->uncheckField("media_library_select_form[$selected_item_id]");
$this->waitForText('1 item selected');
$this->pressInsertSelected('Added one media item.');
$this->waitForText('Another video');
// Assert removing an added oEmbed media item before save works as expected.
$this->openMediaLibraryForField('field_unlimited_media');
$this->switchToMediaType('Five');
$page->fillField('Add Type Five via URL', $youtube_url);
$page->pressButton('Add');
// Assert the media item fields are shown and the vertical tabs are no
// longer shown.
$this->assertMediaAdded();
// Press the 'Remove button' and assert the user is sent back to the media
// library.
$page->pressButton('media-0-remove-button');
// Assert the remove message is shown.
$this->waitForText("The media item $youtube_title has been removed.");
$this->assertNoMediaAdded();
}
/**
* Tests that oEmbed media can be added in the widget's advanced UI.
*
* @todo Merge this with testWidgetOEmbed() in
* https://www.drupal.org/project/drupal/issues/3087227
*/
public function testWidgetOEmbedAdvancedUi(): void {
$this->config('media_library.settings')->set('advanced_ui', TRUE)->save();
$this->hijackProviderEndpoints();
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
$youtube_title = "Everyday I'm Drupalin' Drupal Rap (Rick Ross - Hustlin)";
$youtube_url = 'https://www.youtube.com/watch?v=PWjcqE3QKBg';
$vimeo_title = "Drupal Rap Video - Schipulcon09";
$vimeo_url = 'https://vimeo.com/7073899';
ResourceController::setResourceUrl($youtube_url, $this->getFixturesDirectory() . '/video_youtube.json');
ResourceController::setResourceUrl($vimeo_url, $this->getFixturesDirectory() . '/video_vimeo.json');
ResourceController::setResource404('https://www.youtube.com/watch?v=PWjcqE3QKBg1');
// Visit a node create page.
$this->drupalGet('node/add/basic_page');
// Add to the unlimited media field.
$this->openMediaLibraryForField('field_unlimited_media');
// Assert the default tab for media type one does not have an oEmbed form.
$assert_session->fieldNotExists('Add Type Five via URL');
// Assert other media types don't have the oEmbed form fields.
$this->switchToMediaType('Three');
$assert_session->fieldNotExists('Add Type Five via URL');
// Assert we can add an oEmbed video to media type five.
$this->switchToMediaType('Five');
$page->fillField('Add Type Five via URL', $youtube_url);
$assert_session->pageTextContains('Allowed providers: YouTube, Vimeo.');
$page->pressButton('Add');
// assertWaitOnAjaxRequest() required for input "id" attributes to
// consistently match their label's "for" attribute.
$assert_session->assertWaitOnAjaxRequest();
$this->waitForText('The media item has been created but has not yet been saved.');
// Assert that Additional selected media does not appear.
$assert_session->pageTextNotContains('Additional selected media');
$assert_session->elementNotExists('css', '[data-drupal-selector="edit-selection"]');
// Assert the name field contains the remote video title.
$assert_session->fieldValueEquals('Name', $youtube_title);
$this->saveAnd('select');
$this->waitForText('Add Type Five via URL');
// Load the created media item.
$media_items = Media::loadMultiple();
$added_media = array_pop($media_items);
$added_media_id = $added_media->id();
// Ensure the media item was saved to the library and automatically
// selected. The added media items should be in the first position of the
// add form.
$assert_session->pageTextContains('Add or select media');
$assert_session->pageTextContains($youtube_title);
$assert_session->fieldValueEquals("media_library_select_form[$added_media_id]", $added_media_id);
$assert_session->checkboxChecked("media_library_select_form[$added_media_id]");
// Assert the created oEmbed video is correctly added to the widget.
$this->pressInsertSelected('Added one media item.');
$this->waitForText($youtube_title);
// Open the media library again for the unlimited field and go to the tab
// for media type five.
$this->openMediaLibraryForField('field_unlimited_media');
$this->switchToMediaType('Five');
// Assert the video is available on the tab.
$assert_session->pageTextContains($youtube_title);
// Assert we can only add supported URLs.
$page->fillField('Add Type Five via URL', 'https://www.youtube.com/');
$page->pressButton('Add');
// assertWaitOnAjaxRequest() required for input "id" attributes to
// consistently match their label's "for" attribute.
$assert_session->assertWaitOnAjaxRequest();
$this->waitForText('No matching provider found.');
// Assert we can not add a video ID that doesn't exist. We need to use a
// video ID that will not be filtered by the regex, because otherwise the
// message 'No matching provider found.' will be returned.
$page->fillField('Add Type Five via URL', 'https://www.youtube.com/watch?v=PWjcqE3QKBg1');
$page->pressButton('Add');
// assertWaitOnAjaxRequest() required for input "id" attributes to
// consistently match their label's "for" attribute.
$assert_session->assertWaitOnAjaxRequest();
$this->waitForText('Could not retrieve the oEmbed resource.');
// Select a media item to check if the selection is persisted when adding
// new items.
$checkbox = $page->findField("Select $youtube_title");
$selected_item_id = $checkbox->getAttribute('value');
$checkbox->click();
$assert_session->pageTextContains('1 item selected');
$assert_session->hiddenFieldValueEquals('current_selection', $selected_item_id);
// Assert we can add a oEmbed video with a custom name.
$page->fillField('Add Type Five via URL', $youtube_url);
$page->pressButton('Add');
// assertWaitOnAjaxRequest() required for input "id" attributes to
// consistently match their label's "for" attribute.
$assert_session->assertWaitOnAjaxRequest();
$this->waitForText('The media item has been created but has not yet been saved.');
// The advanced ui should show the Additional selected media.
$assert_session->pageTextContains('Additional selected media');
$assert_session->elementExists('css', '[data-drupal-selector="edit-selection"]');
$page->fillField('Name', 'Custom video title');
$assert_session->checkboxChecked("Select $youtube_title", $this->getSelectionArea());
$this->saveAnd('select');
$this->waitForNoText('Save and select');
// Load the created media item.
$media_items = Media::loadMultiple();
$added_media = array_pop($media_items);
$added_media_id = $added_media->id();
// Ensure the media item was saved to the library and automatically
// selected. The added media items should be in the first position of the
// add form.
$assert_session->pageTextContains('Add or select media');
$assert_session->pageTextContains('Custom video title');
$assert_session->fieldValueEquals("media_library_select_form[$added_media_id]", $added_media_id);
$assert_session->checkboxChecked("media_library_select_form[$added_media_id]");
// Assert the item that was selected before uploading the file is still
// selected.
$assert_session->pageTextContains('2 items selected');
$assert_session->checkboxChecked("Select Custom video title");
$assert_session->checkboxChecked("Select $youtube_title");
$assert_session->hiddenFieldValueEquals('current_selection', implode(',', [$selected_item_id, $added_media_id]));
$selected_checkboxes = [];
foreach ($this->getCheckboxes() as $checkbox) {
if ($checkbox->isChecked()) {
$selected_checkboxes[] = $checkbox->getAttribute('value');
}
}
$this->assertCount(2, $selected_checkboxes);
// Ensure the created item is added in the widget.
$this->pressInsertSelected('Added 2 media items.');
$this->waitForText('Custom video title');
// Assert we can directly insert added oEmbed media in the widget.
$this->openMediaLibraryForField('field_unlimited_media');
$this->switchToMediaType('Five');
$page->fillField('Add Type Five via URL', $vimeo_url);
$page->pressButton('Add');
$this->waitForText('The media item has been created but has not yet been saved.');
$this->saveAnd('insert');
$this->waitForText('Added one media item.');
$this->waitForNoText('Add or select media');
$this->waitForText($vimeo_title);
// Assert we can remove selected items from the selection area in the oEmbed
// form.
$this->openMediaLibraryForField('field_unlimited_media');
$this->switchToMediaType('Five');
$checkbox = $page->findField("Select $vimeo_title");
$selected_item_id = $checkbox->getAttribute('value');
$checkbox->click();
$assert_session->hiddenFieldValueEquals('current_selection', $selected_item_id);
$page->fillField('Add Type Five via URL', $youtube_url);
$page->pressButton('Add');
// assertWaitOnAjaxRequest() required for input "id" attributes to
// consistently match their label's "for" attribute.
$assert_session->assertWaitOnAjaxRequest();
$this->waitForText('The media item has been created but has not yet been saved');
$page->fillField('Name', 'Another video');
$selection_area = $this->getSelectionArea();
$assert_session->checkboxChecked("Select $vimeo_title", $selection_area);
$page->uncheckField("Select $vimeo_title");
$assert_session->hiddenFieldValueEquals('current_selection', '');
// Close the details element so that clicking the Save and select works.
// @todo Fix dialog or test so this is not necessary to prevent random
// fails. https://www.drupal.org/project/drupal/issues/3055648
$selection_area->find('css', 'summary')->click();
$this->saveAnd('select');
$media_items = Media::loadMultiple();
$added_media = array_pop($media_items);
$added_media_id = $added_media->id();
$this->waitForText('1 item selected');
$assert_session->checkboxChecked('Select Another video');
$assert_session->checkboxNotChecked("Select $vimeo_title");
$assert_session->hiddenFieldValueEquals('current_selection', $added_media_id);
$this->pressInsertSelected('Added one media item.');
$this->waitForText('Another video');
// Assert removing an added oEmbed media item before save works as expected.
$this->openMediaLibraryForField('field_unlimited_media');
$this->switchToMediaType('Five');
$page->fillField('Add Type Five via URL', $youtube_url);
$page->pressButton('Add');
// Assert the media item fields are shown and the vertical tabs are no
// longer shown.
$this->assertMediaAdded();
// Press the 'Remove button' and assert the user is sent back to the media
// library.
$page->pressButton('media-0-remove-button');
// Assert the remove message is shown.
$this->waitForText("The media item $youtube_title has been removed.");
$this->assertNoMediaAdded();
}
}

View File

@@ -0,0 +1,185 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media_library\FunctionalJavascript;
use Drupal\Tests\TestFileCreationTrait;
/**
* Tests that uploads in the 'media_library_widget' works as expected.
*
* @group media_library
*
* @todo This test will occasionally fail with SQLite until
* https://www.drupal.org/node/3066447 is addressed.
*/
class WidgetOverflowTest extends MediaLibraryTestBase {
use TestFileCreationTrait;
/**
* The test image file.
*
* @var \Drupal\file\Entity\File
*/
protected $image;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
foreach ($this->getTestFiles('image') as $image) {
$extension = pathinfo($image->filename, PATHINFO_EXTENSION);
if ($extension === 'png') {
$this->image = $image;
}
}
if (!isset($this->image)) {
$this->fail('Expected test files not present.');
}
// Create a user that can only add media of type four.
$user = $this->drupalCreateUser([
'access administration pages',
'access content',
'create basic_page content',
'create type_one media',
'create type_three media',
'view media',
]);
$this->drupalLogin($user);
}
/**
* Uploads multiple test images into the media library.
*
* @param int $number
* The number of images to upload.
*/
private function uploadFiles(int $number): void {
/** @var \Drupal\Core\File\FileSystemInterface $file_system */
$file_system = $this->container->get('file_system');
// Create a list of new files to upload.
$filenames = $remote_paths = [];
for ($i = 0; $i < $number; $i++) {
$path = $file_system->copy($this->image->uri, 'public://');
$path = $file_system->realpath($path);
$this->assertNotEmpty($path);
$this->assertFileExists($path);
$filenames[] = $file_system->basename($path);
$remote_paths[] = $this->getSession()
->getDriver()
->uploadFileAndGetRemoteFilePath($path);
}
$page = $this->getSession()->getPage();
$page->fillField('Add files', implode("\n", $remote_paths));
$this->assertMediaAdded();
$assert_session = $this->assertSession();
foreach ($filenames as $i => $filename) {
$assert_session->fieldValueEquals("media[$i][fields][name][0][value]", $filename);
$page->fillField("media[$i][fields][field_media_test_image][0][alt]", $filename);
}
}
/**
* Tests that the Media Library constrains the number of selected items.
*
* @param string|null $selected_operation
* The operation of the button to click. For example, if this is "insert",
* the "Save and insert" button will be pressed. If NULL, the "Save" button
* will be pressed.
*
* @dataProvider providerWidgetOverflow
*/
public function testWidgetOverflow(?string $selected_operation): void {
// If we want to press the "Save and insert" or "Save and select" buttons,
// we need to enable the advanced UI.
if ($selected_operation) {
$this->config('media_library.settings')->set('advanced_ui', TRUE)->save();
}
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
$this->drupalGet('node/add/basic_page');
// Upload 5 files into a media field that only allows 2.
$this->openMediaLibraryForField('field_twin_media');
$this->uploadFiles(5);
// Save the media items and ensure that the user is warned that they have
// selected too many items.
if ($selected_operation) {
$this->saveAnd($selected_operation);
}
else {
$this->pressSaveButton();
}
$this->waitForElementTextContains('.messages--warning', 'There are currently 5 items selected. The maximum number of items for the field is 2. Remove 3 items from the selection.');
// If the user tries to insert the selected items anyway, they should get
// an error.
$this->pressInsertSelected(NULL, FALSE);
$this->waitForElementTextContains('.messages--error', 'There are currently 5 items selected. The maximum number of items for the field is 2. Remove 3 items from the selection.');
$assert_session->elementNotExists('css', '.messages--warning');
// Once the extra items are deselected, all should be well.
$this->deselectMediaItem(2);
$this->deselectMediaItem(3);
$this->deselectMediaItem(4);
$this->pressInsertSelected('Added 2 media items.');
}
/**
* Tests that unlimited fields' selection count is not constrained.
*
* @param string|null $selected_operation
* The operation of the button to click. For example, if this is "insert",
* the "Save and insert" button will be pressed. If NULL, the "Save" button
* will be pressed.
*
* @dataProvider providerWidgetOverflow
*/
public function testUnlimitedCardinality(?string $selected_operation): void {
if ($selected_operation) {
$this->config('media_library.settings')->set('advanced_ui', TRUE)->save();
}
$assert_session = $this->assertSession();
// Visit a node create page and open the media library.
$this->drupalGet('node/add/basic_page');
$this->openMediaLibraryForField('field_unlimited_media');
$this->switchToMediaType('Three');
$this->uploadFiles(5);
if ($selected_operation) {
$this->saveAnd($selected_operation);
}
else {
$this->pressSaveButton();
}
if ($selected_operation !== 'insert') {
$this->pressInsertSelected();
}
// There should not be any warnings or errors.
$assert_session->elementNotExists('css', '.messages--error');
$assert_session->elementNotExists('css', '.messages--warning');
$this->waitForText('Added 5 media items.');
}
/**
* Data provider for ::testWidgetOverflow() and ::testUnlimitedCardinality().
*
* @return array[]
* Sets of arguments to pass to the test method.
*/
public static function providerWidgetOverflow(): array {
return [
'Save' => [NULL],
'Save and insert' => ['insert'],
'Save and select' => ['select'],
];
}
}

View File

@@ -0,0 +1,763 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media_library\FunctionalJavascript;
use Drupal\media\Entity\Media;
use Drupal\Tests\TestFileCreationTrait;
/**
* Tests that uploads in the 'media_library_widget' works as expected.
*
* @group media_library
*
* @todo This test will occasionally fail with SQLite until
* https://www.drupal.org/node/3066447 is addressed.
*/
class WidgetUploadTest extends MediaLibraryTestBase {
use TestFileCreationTrait;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests that uploads in the 'media_library_widget' works as expected.
*/
public function testWidgetUpload(): void {
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
$driver = $this->getSession()->getDriver();
foreach ($this->getTestFiles('image') as $image) {
$extension = pathinfo($image->filename, PATHINFO_EXTENSION);
if ($extension === 'png') {
$png_image = $image;
}
elseif ($extension === 'jpg') {
$jpg_image = $image;
}
}
if (!isset($png_image) || !isset($jpg_image)) {
$this->fail('Expected test files not present.');
}
// Create a user that can only add media of type four.
$user = $this->drupalCreateUser([
'access administration pages',
'access content',
'create basic_page content',
'create type_one media',
'create type_four media',
'view media',
]);
$this->drupalLogin($user);
// Visit a node create page and open the media library.
$this->drupalGet('node/add/basic_page');
$this->openMediaLibraryForField('field_twin_media');
// Assert the upload form is not visible for default tab type_three without
// the proper permissions.
$assert_session->elementNotExists('css', '.js-media-library-add-form');
// Assert the upload form is not visible for the non-file based media type
// type_one.
$this->switchToMediaType('One');
$assert_session->elementNotExists('css', '.js-media-library-add-form');
// Assert the upload form is visible for type_four.
$this->switchToMediaType('Four');
$assert_session->fieldExists('Add files');
$assert_session->pageTextContains('Maximum 2 files.');
// Create a user that can create media for all media types.
$user = $this->drupalCreateUser([
'access administration pages',
'access content',
'create basic_page content',
'create media',
'view media',
]);
$this->drupalLogin($user);
// Visit a node create page.
$this->drupalGet('node/add/basic_page');
$file_storage = $this->container->get('entity_type.manager')->getStorage('file');
/** @var \Drupal\Core\File\FileSystemInterface $file_system */
$file_system = $this->container->get('file_system');
// Add to the twin media field.
$this->openMediaLibraryForField('field_twin_media');
// Assert the upload form is now visible for default tab type_three.
$assert_session->elementExists('css', '.js-media-library-add-form');
$assert_session->fieldExists('Add files');
// Assert we can upload a file to the default tab type_three.
$assert_session->elementNotExists('css', '.js-media-library-add-form[data-input]');
$this->addMediaFileToField('Add files', $this->container->get('file_system')->realpath($png_image->uri));
$this->assertMediaAdded();
$assert_session->elementExists('css', '.js-media-library-add-form[data-input]');
// We do not have pre-selected items, so the container should not be added
// to the form.
$assert_session->pageTextNotContains('Additional selected media');
// Files are temporary until the form is saved.
$files = $file_storage->loadMultiple();
$file = array_pop($files);
$this->assertSame('public://type-three-dir', $file_system->dirname($file->getFileUri()));
$this->assertTrue($file->isTemporary());
// Assert the revision_log_message field is not shown.
$upload_form = $assert_session->elementExists('css', '.js-media-library-add-form');
$assert_session->fieldNotExists('Revision log message', $upload_form);
// Assert the name field contains the filename and the alt text is required.
$assert_session->fieldValueEquals('Name', $png_image->filename);
$this->pressSaveButton(TRUE);
$this->waitForText('Alternative text field is required');
$page->fillField('Alternative text', $this->randomString());
$this->pressSaveButton();
$this->assertJsCondition('jQuery("input[name=\'media_library_select_form[1]\']").is(":focus")');
// The file should be permanent now.
$files = $file_storage->loadMultiple();
$file = array_pop($files);
$this->assertFalse($file->isTemporary());
// Load the created media item.
$media_items = Media::loadMultiple();
$added_media = array_pop($media_items);
$added_media_id = $added_media->id();
// Ensure the media item was saved to the library and automatically
// selected. The added media items should be in the first position of the
// add form.
$assert_session->pageTextContains('Add or select media');
$assert_session->pageTextContains($png_image->filename);
$assert_session->fieldValueEquals("media_library_select_form[$added_media_id]", $added_media_id);
$assert_session->checkboxChecked("media_library_select_form[$added_media_id]");
$assert_session->pageTextContains('1 of 2 items selected');
$assert_session->hiddenFieldValueEquals('current_selection', $added_media_id);
// Ensure the created item is added in the widget.
$this->pressInsertSelected('Added one media item.');
$this->waitForText($png_image->filename);
// Remove the item.
$assert_session->elementTextContains('css', '.field--name-field-twin-media', $png_image->filename);
$assert_session->elementExists('css', '.field--name-field-twin-media')->pressButton('Remove');
$this->waitForElementTextContains('#drupal-live-announce', $png_image->filename . ' has been removed');
$assert_session->elementTextNotContains('css', '.field--name-field-twin-media', $png_image->filename);
$this->openMediaLibraryForField('field_twin_media');
$this->switchToMediaType('Three');
$png_uri_2 = $file_system->copy($png_image->uri, 'public://');
$this->addMediaFileToField('Add files', $this->container->get('file_system')->realpath($png_uri_2));
$this->waitForFieldExists('Alternative text')->setValue($this->randomString());
$this->pressSaveButton();
$this->pressInsertSelected('Added one media item.');
$this->waitForText($file_system->basename($png_uri_2));
// Also make sure that we can upload to the unlimited cardinality field.
$this->openMediaLibraryForField('field_unlimited_media');
$this->switchToMediaType('Three');
// Select a media item to check if the selection is persisted when adding
// new items.
$existing_media_name = $file_system->basename($png_uri_2);
$checkbox = $page->findField("Select $existing_media_name");
$selected_item_id = $checkbox->getAttribute('value');
$checkbox->click();
$assert_session->pageTextContains('1 item selected');
$assert_session->hiddenFieldValueEquals('current_selection', $selected_item_id);
$png_uri_3 = $file_system->copy($png_image->uri, 'public://');
$this->addMediaFileToField('Add files', $this->container->get('file_system')->realpath($png_uri_3));
$this->waitForText('The media item has been created but has not yet been saved.');
$page->fillField('Name', 'Unlimited Cardinality Image');
$page->fillField('Alternative text', $this->randomString());
$this->pressSaveButton();
// Load the created media item.
$media_items = Media::loadMultiple();
$added_media = array_pop($media_items);
$added_media_name = $added_media->label();
$added_media_id = $added_media->id();
// Ensure the media item was saved to the library and automatically
// selected. The added media items should be in the first position of the
// add form.
$assert_session->pageTextContains('Add or select media');
$assert_session->pageTextContains('Unlimited Cardinality Image');
$assert_session->fieldValueEquals("media_library_select_form[$added_media_id]", $added_media_id);
$assert_session->checkboxChecked("media_library_select_form[$added_media_id]");
// Assert the item that was selected before uploading the file is still
// selected.
$assert_session->pageTextContains('2 items selected');
$assert_session->checkboxChecked("Select $added_media_name");
$assert_session->checkboxChecked("Select $existing_media_name");
$assert_session->hiddenFieldValueEquals('current_selection', implode(',', [$selected_item_id, $added_media_id]));
$selected_checkboxes = [];
foreach ($this->getCheckboxes() as $checkbox) {
if ($checkbox->isChecked()) {
$selected_checkboxes[] = $checkbox->getAttribute('value');
}
}
$this->assertCount(2, $selected_checkboxes);
// Ensure the created item is added in the widget.
$this->pressInsertSelected('Added 2 media items.');
$this->waitForText('Unlimited Cardinality Image');
// Assert we can now only upload one more media item.
$this->openMediaLibraryForField('field_twin_media');
$this->switchToMediaType('Four');
// We set the multiple to FALSE if only one file can be uploaded
$this->assertFalse($assert_session->fieldExists('Add file')->hasAttribute('multiple'));
$assert_session->pageTextContains('One file only.');
$choose_files = $assert_session->elementExists('css', '.form-managed-file');
$choose_files->hasButton('Choose file');
$this->assertFalse($choose_files->hasButton('Choose files'));
// Assert media type four should only allow jpg files by trying a png file
// first.
$png_uri_4 = $file_system->copy($png_image->uri, 'public://');
$this->addMediaFileToField('Add file', $file_system->realpath($png_uri_4));
$this->waitForText('Only files with the following extensions are allowed');
// Assert that jpg files are accepted by type four.
$jpg_uri_2 = $file_system->copy($jpg_image->uri, 'public://');
$this->addMediaFileToField('Add file', $file_system->realpath($jpg_uri_2));
$this->waitForFieldExists('Alternative text')->setValue($this->randomString());
// The type_four media type has another optional image field.
$assert_session->pageTextContains('Extra Image');
$jpg_uri_3 = $file_system->copy($jpg_image->uri, 'public://');
$this->addMediaFileToField('Extra Image', $this->container->get('file_system')->realpath($jpg_uri_3));
$this->waitForText($file_system->basename($jpg_uri_3));
// Ensure that the extra image was uploaded to the correct directory.
$files = $file_storage->loadMultiple();
$file = array_pop($files);
$this->assertSame('public://type-four-extra-dir', $file_system->dirname($file->getFileUri()));
$this->pressSaveButton();
// Ensure the media item was saved to the library and automatically
// selected.
$this->waitForText('Add or select media');
$this->waitForText($file_system->basename($jpg_uri_2));
// Ensure the created item is added in the widget.
$this->pressInsertSelected('Added one media item.');
$assert_session->pageTextContains($file_system->basename($jpg_uri_2));
// Assert we can also remove selected items from the selection area in the
// upload form.
$this->openMediaLibraryForField('field_unlimited_media');
$this->switchToMediaType('Three');
$checkbox = $page->findField("Select $existing_media_name");
$selected_item_id = $checkbox->getAttribute('value');
$checkbox->click();
$assert_session->hiddenFieldValueEquals('current_selection', $selected_item_id);
$this->assertTrue($assert_session->fieldExists('Add files')->hasAttribute('multiple'));
$png_uri_5 = $file_system->copy($png_image->uri, 'public://');
$this->addMediaFileToField('Add files', $this->container->get('file_system')->realpath($png_uri_5));
// assertWaitOnAjaxRequest() required for input "id" attributes to
// consistently match their label's "for" attribute.
$assert_session->assertWaitOnAjaxRequest();
$page->fillField('Alternative text', $this->randomString());
$this->pressSaveButton();
$page->uncheckField('media_library_select_form[2]');
$this->waitForText('1 item selected');
$this->waitForText("Select $existing_media_name");
$media_items = Media::loadMultiple();
$added_media = array_pop($media_items);
$added_media_name = $added_media->label();
$added_media_id = $added_media->id();
$assert_session->pageTextContains('1 item selected');
$assert_session->checkboxChecked("Select $added_media_name");
$assert_session->checkboxNotChecked("Select $existing_media_name");
$assert_session->hiddenFieldValueEquals('current_selection', $added_media_id);
$this->pressInsertSelected('Added one media item.');
$this->waitForText($file_system->basename($png_uri_5));
// Assert removing an uploaded media item before save works as expected.
$this->openMediaLibraryForField('field_unlimited_media');
$this->switchToMediaType('Three');
$this->addMediaFileToField('Add files', $this->container->get('file_system')->realpath($png_image->uri));
// Assert the media item fields are shown and the vertical tabs are no
// longer shown.
$this->waitForFieldExists('Alternative text');
$this->assertMediaAdded();
// Press the 'Remove button' and assert the user is sent back to the media
// library.
$page->pressButton('media-0-remove-button');
// Assert the remove message is shown.
$this->waitForText("The media item $png_image->filename has been removed.");
// Assert the focus is shifted to the first tabbable element of the add
// form, which should be the source field.
$this->assertNoMediaAdded();
$assert_session->elementExists('css', '.ui-dialog-titlebar-close')->click();
// Assert uploading multiple files.
$this->openMediaLibraryForField('field_unlimited_media');
$this->switchToMediaType('Three');
// Assert the existing items are remembered when adding and removing media.
$checkbox = $page->findField("Select $existing_media_name");
$checkbox->click();
// Assert we can add multiple files.
$this->assertTrue($assert_session->fieldExists('Add files')->hasAttribute('multiple'));
// Create a list of new files to upload.
$filenames = [];
$remote_paths = [];
foreach (range(1, 4) as $i) {
$path = $file_system->copy($png_image->uri, 'public://');
$filenames[] = $file_system->basename($path);
$remote_paths[] = $driver->uploadFileAndGetRemoteFilePath($file_system->realpath($path));
}
$page->findField('Add files')->setValue(implode("\n", $remote_paths));
// Assert the media item fields are shown and the vertical tabs are no
// longer shown.
$this->assertMediaAdded();
// Assert all files have been added.
$assert_session->fieldValueEquals('media[0][fields][name][0][value]', $filenames[0]);
$assert_session->fieldValueEquals('media[1][fields][name][0][value]', $filenames[1]);
$assert_session->fieldValueEquals('media[2][fields][name][0][value]', $filenames[2]);
$assert_session->fieldValueEquals('media[3][fields][name][0][value]', $filenames[3]);
// Set alt texts for items 1 and 2, leave the alt text empty for items 3
// and 4 to assert the field validation does not stop users from removing
// items.
$page->fillField('media[0][fields][field_media_test_image][0][alt]', $filenames[0]);
$page->fillField('media[1][fields][field_media_test_image][0][alt]', $filenames[1]);
// Assert the file is available in the file storage.
$files = $file_storage->loadByProperties(['filename' => $filenames[1]]);
$this->assertCount(1, $files);
$file_1_uri = reset($files)->getFileUri();
// Remove the second file and assert the focus is shifted to the container
// of the next media item and field values are still correct.
$page->pressButton('media-1-remove-button');
$this->assertTrue($assert_session->waitForText('The media item ' . $filenames[1] . ' has been removed.'));
$this->assertJsCondition('jQuery("[data-media-library-added-delta=2]").is(":focus")');
// Assert the file was deleted.
$this->assertEmpty($file_storage->loadByProperties(['filename' => $filenames[1]]));
$this->assertFileDoesNotExist($file_1_uri);
// When a file is already in usage, it should not be deleted. To test,
// let's add a usage for $filenames[3] (now in the third position).
$files = $file_storage->loadByProperties(['filename' => $filenames[3]]);
$this->assertCount(1, $files);
$target_file = reset($files);
Media::create([
'bundle' => 'type_three',
'name' => 'Disturbing',
'field_media_test_image' => [
['target_id' => $target_file->id()],
],
])->save();
// Remove $filenames[3] (now in the third position) and assert the focus is
// shifted to the container of the previous media item and field values are
// still correct.
$page->pressButton('media-3-remove-button');
$this->assertTrue($assert_session->waitForText('The media item ' . $filenames[3] . ' has been removed.'));
// Assert the file was not deleted, due to being in use elsewhere.
$this->assertNotEmpty($file_storage->loadByProperties(['filename' => $filenames[3]]));
$this->assertFileExists($target_file->getFileUri());
// The second media item should be removed (this has the delta 1 since we
// start counting from 0).
$assert_session->elementNotExists('css', '[data-media-library-added-delta=1]');
$media_item_one = $assert_session->elementExists('css', '[data-media-library-added-delta=0]');
$assert_session->fieldValueEquals('Name', $filenames[0], $media_item_one);
$assert_session->fieldValueEquals('Alternative text', $filenames[0], $media_item_one);
$media_item_three = $assert_session->elementExists('css', '[data-media-library-added-delta=2]');
$assert_session->fieldValueEquals('Name', $filenames[2], $media_item_three);
$assert_session->fieldValueEquals('Alternative text', '', $media_item_three);
}
/**
* Tests that uploads in the widget's advanced UI works as expected.
*
* @todo Merge this with testWidgetUpload() in
* https://www.drupal.org/project/drupal/issues/3087227
*/
public function testWidgetUploadAdvancedUi(): void {
$this->config('media_library.settings')->set('advanced_ui', TRUE)->save();
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
$driver = $this->getSession()->getDriver();
foreach ($this->getTestFiles('image') as $image) {
$extension = pathinfo($image->filename, PATHINFO_EXTENSION);
if ($extension === 'png') {
$png_image = $image;
}
elseif ($extension === 'jpg') {
$jpg_image = $image;
}
}
if (!isset($png_image) || !isset($jpg_image)) {
$this->fail('Expected test files not present.');
}
// Create a user that can only add media of type four.
$user = $this->drupalCreateUser([
'access administration pages',
'access content',
'create basic_page content',
'create type_one media',
'create type_four media',
'view media',
]);
$this->drupalLogin($user);
// Visit a node create page and open the media library.
$this->drupalGet('node/add/basic_page');
$this->openMediaLibraryForField('field_twin_media');
// Assert the upload form is not visible for default tab type_three without
// the proper permissions.
$assert_session->elementNotExists('css', '.js-media-library-add-form');
// Assert the upload form is not visible for the non-file based media type
// type_one.
$this->switchToMediaType('One');
$assert_session->elementNotExists('css', '.js-media-library-add-form');
// Assert the upload form is visible for type_four.
$this->switchToMediaType('Four');
$assert_session->fieldExists('Add files');
$assert_session->pageTextContains('Maximum 2 files.');
// Create a user that can create media for all media types.
$user = $this->drupalCreateUser([
'access administration pages',
'access content',
'create basic_page content',
'create media',
'view media',
]);
$this->drupalLogin($user);
// Visit a node create page.
$this->drupalGet('node/add/basic_page');
$file_storage = $this->container->get('entity_type.manager')->getStorage('file');
/** @var \Drupal\Core\File\FileSystemInterface $file_system */
$file_system = $this->container->get('file_system');
// Add to the twin media field.
$this->openMediaLibraryForField('field_twin_media');
// Assert the upload form is now visible for default tab type_three.
$assert_session->elementExists('css', '.js-media-library-add-form');
$assert_session->fieldExists('Add files');
// Assert we can upload a file to the default tab type_three.
$assert_session->elementNotExists('css', '.js-media-library-add-form[data-input]');
$this->addMediaFileToField('Add files', $this->container->get('file_system')->realpath($png_image->uri));
$this->assertMediaAdded();
$assert_session->elementExists('css', '.js-media-library-add-form[data-input]');
// We do not have a pre-selected items, so the container should not be added
// to the form.
$assert_session->elementNotExists('css', 'details summary:contains(Additional selected media)');
// Files are temporary until the form is saved.
$files = $file_storage->loadMultiple();
$file = array_pop($files);
$this->assertSame('public://type-three-dir', $file_system->dirname($file->getFileUri()));
$this->assertTrue($file->isTemporary());
// Assert the revision_log_message field is not shown.
$upload_form = $assert_session->elementExists('css', '.js-media-library-add-form');
$assert_session->fieldNotExists('Revision log message', $upload_form);
// Assert the name field contains the filename and the alt text is required.
$assert_session->fieldValueEquals('Name', $png_image->filename);
$this->saveAnd('select');
$this->waitForText('Alternative text field is required');
$page->fillField('Alternative text', $this->randomString());
$this->saveAnd('select');
$this->assertJsCondition('jQuery("input[name=\'media_library_select_form[1]\']").is(":focus")');
// The file should be permanent now.
$files = $file_storage->loadMultiple();
$file = array_pop($files);
$this->assertFalse($file->isTemporary());
// Load the created media item.
$media_items = Media::loadMultiple();
$added_media = array_pop($media_items);
$added_media_id = $added_media->id();
// Ensure the media item was saved to the library and automatically
// selected. The added media items should be in the first position of the
// add form.
$assert_session->pageTextContains('Add or select media');
$assert_session->pageTextContains($png_image->filename);
$assert_session->fieldValueEquals("media_library_select_form[$added_media_id]", $added_media_id);
$assert_session->checkboxChecked("media_library_select_form[$added_media_id]");
$assert_session->pageTextContains('1 of 2 items selected');
$assert_session->hiddenFieldValueEquals('current_selection', $added_media_id);
// Ensure the created item is added in the widget.
$this->pressInsertSelected('Added one media item.');
$this->waitForText($png_image->filename);
// Remove the item.
$assert_session->elementTextContains('css', '.field--name-field-twin-media', $png_image->filename);
$assert_session->elementExists('css', '.field--name-field-twin-media')->pressButton('Remove');
$this->waitForElementTextContains('#drupal-live-announce', $png_image->filename . ' has been removed');
$assert_session->elementTextNotContains('css', '.field--name-field-twin-media', $png_image->filename);
$this->openMediaLibraryForField('field_twin_media');
$this->switchToMediaType('Three');
$png_uri_2 = $file_system->copy($png_image->uri, 'public://');
$this->addMediaFileToField('Add files', $this->container->get('file_system')->realpath($png_uri_2));
$this->waitForFieldExists('Alternative text')->setValue($this->randomString());
// Assert we can also directly insert uploaded files in the widget.
$this->saveAnd('insert');
$this->waitForText('Added one media item.');
$this->waitForNoText('Add or select media');
$this->waitForText($file_system->basename($png_uri_2));
// Also make sure that we can upload to the unlimited cardinality field.
$this->openMediaLibraryForField('field_unlimited_media');
$this->switchToMediaType('Three');
// Select a media item to check if the selection is persisted when adding
// new items.
$existing_media_name = $file_system->basename($png_uri_2);
$checkbox = $page->findField("Select $existing_media_name");
$selected_item_id = $checkbox->getAttribute('value');
$checkbox->click();
$assert_session->pageTextContains('1 item selected');
$assert_session->hiddenFieldValueEquals('current_selection', $selected_item_id);
$png_uri_3 = $file_system->copy($png_image->uri, 'public://');
$this->addMediaFileToField('Add files', $this->container->get('file_system')->realpath($png_uri_3));
$this->waitForText('The media item has been created but has not yet been saved.');
$assert_session->checkboxChecked("Select $existing_media_name");
$page->fillField('Name', 'Unlimited Cardinality Image');
$page->fillField('Alternative text', $this->randomString());
$this->saveAnd('select');
$this->waitForNoText('Save and select');
// Load the created media item.
$media_items = Media::loadMultiple();
$added_media = array_pop($media_items);
$added_media_name = $added_media->label();
$added_media_id = $added_media->id();
// Ensure the media item was saved to the library and automatically
// selected. The added media items should be in the first position of the
// add form.
$assert_session->pageTextContains('Add or select media');
$assert_session->pageTextContains('Unlimited Cardinality Image');
$assert_session->fieldValueEquals("media_library_select_form[$added_media_id]", $added_media_id);
$assert_session->checkboxChecked("media_library_select_form[$added_media_id]");
// Assert the item that was selected before uploading the file is still
// selected.
$assert_session->pageTextContains('2 items selected');
$assert_session->checkboxChecked("Select $added_media_name");
$assert_session->checkboxChecked("Select $existing_media_name");
$assert_session->hiddenFieldValueEquals('current_selection', implode(',', [$selected_item_id, $added_media_id]));
$selected_checkboxes = [];
foreach ($this->getCheckboxes() as $checkbox) {
if ($checkbox->isChecked()) {
$selected_checkboxes[] = $checkbox->getAttribute('value');
}
}
$this->assertCount(2, $selected_checkboxes);
// Ensure the created item is added in the widget.
$this->pressInsertSelected('Added 2 media items.');
$this->waitForText('Unlimited Cardinality Image');
// Assert we can now only upload one more media item.
$this->openMediaLibraryForField('field_twin_media');
$this->switchToMediaType('Four');
// We set the multiple to FALSE if only one file can be uploaded
$this->assertFalse($assert_session->fieldExists('Add file')->hasAttribute('multiple'));
$assert_session->pageTextContains('One file only.');
$choose_files = $assert_session->elementExists('css', '.form-managed-file');
$choose_files->hasButton('Choose file');
$this->assertFalse($choose_files->hasButton('Choose files'));
// Assert media type four should only allow jpg files by trying a png file
// first.
$png_uri_4 = $file_system->copy($png_image->uri, 'public://');
$this->addMediaFileToField('Add file', $file_system->realpath($png_uri_4));
$this->waitForText('Only files with the following extensions are allowed');
// Assert that jpg files are accepted by type four.
$jpg_uri_2 = $file_system->copy($jpg_image->uri, 'public://');
$this->addMediaFileToField('Add file', $file_system->realpath($jpg_uri_2));
$this->waitForFieldExists('Alternative text')->setValue($this->randomString());
// The type_four media type has another optional image field.
$assert_session->pageTextContains('Extra Image');
$jpg_uri_3 = $file_system->copy($jpg_image->uri, 'public://');
$this->addMediaFileToField('Extra Image', $this->container->get('file_system')->realpath($jpg_uri_3));
$this->waitForText($file_system->basename($jpg_uri_3));
// Ensure that the extra image was uploaded to the correct directory.
$files = $file_storage->loadMultiple();
$file = array_pop($files);
$this->assertSame('public://type-four-extra-dir', $file_system->dirname($file->getFileUri()));
$this->saveAnd('select');
// Ensure the media item was saved to the library and automatically
// selected.
$this->waitForText('Add or select media');
$this->waitForText($file_system->basename($jpg_uri_2));
// Ensure the created item is added in the widget.
$this->pressInsertSelected('Added one media item.');
$assert_session->pageTextContains($file_system->basename($jpg_uri_2));
// Assert users can not select media items they do not have access to.
$unpublished_media = Media::create([
'name' => 'Mosquito',
'bundle' => 'type_one',
'field_media_test' => 'Mosquito',
'status' => FALSE,
]);
$unpublished_media->save();
$this->openMediaLibraryForField('field_unlimited_media');
$this->switchToMediaType('Three');
// Set the hidden field with the current selection via JavaScript and upload
// a file.
$this->getSession()->executeScript("jQuery('.js-media-library-add-form-current-selection').val('1,2,{$unpublished_media->id()}')");
$this->addMediaFileToField('Add files', $this->container->get('file_system')->realpath($png_uri_3));
$this->assertMediaAdded();
// Assert the pre-selected items are shown.
$this->getSelectionArea();
// Assert the published items are selected and the unpublished item is not
// selected.
$this->waitForText(Media::load(1)->label());
$this->waitForText(Media::load(2)->label());
$assert_session->pageTextNotContains('Mosquito');
$page->find('css', '.ui-dialog-titlebar-close')->click();
// Assert we can also remove selected items from the selection area in the
// upload form.
$this->openMediaLibraryForField('field_unlimited_media');
$this->switchToMediaType('Three');
$checkbox = $page->findField("Select $existing_media_name");
$selected_item_id = $checkbox->getAttribute('value');
$checkbox->click();
$assert_session->hiddenFieldValueEquals('current_selection', $selected_item_id);
$this->assertTrue($assert_session->fieldExists('Add files')->hasAttribute('multiple'));
$png_uri_5 = $file_system->copy($png_image->uri, 'public://');
$this->addMediaFileToField('Add files', $this->container->get('file_system')->realpath($png_uri_5));
$this->assertMediaAdded();
$page->fillField('Alternative text', $this->randomString());
// Assert the pre-selected items are shown.
$selection_area = $this->getSelectionArea();
$assert_session->checkboxChecked("Select $existing_media_name", $selection_area);
$selection_area->uncheckField("Select $existing_media_name");
$page->waitFor(10, function () use ($page) {
return $page->find('hidden_field_selector', ['hidden_field', 'current_selection'])->getValue() === '';
});
// Close the details element so that clicking the Save and select works.
// @todo Fix dialog or test so this is not necessary to prevent random
// fails. https://www.drupal.org/project/drupal/issues/3055648
$selection_area->find('css', 'summary')->click();
$this->saveAnd('select');
$this->waitForText("Select $existing_media_name");
$media_items = Media::loadMultiple();
$added_media = array_pop($media_items);
$added_media_name = $added_media->label();
$added_media_id = $added_media->id();
$assert_session->pageTextContains('1 item selected');
$assert_session->checkboxChecked("Select $added_media_name");
$assert_session->checkboxNotChecked("Select $existing_media_name");
$assert_session->hiddenFieldValueEquals('current_selection', $added_media_id);
$this->pressInsertSelected('Added one media item.');
$this->waitForText($file_system->basename($png_uri_5));
// Assert removing an uploaded media item before save works as expected.
$this->openMediaLibraryForField('field_unlimited_media');
$this->switchToMediaType('Three');
$this->addMediaFileToField('Add files', $this->container->get('file_system')->realpath($png_image->uri));
// Assert the media item fields are shown and the vertical tabs are no
// longer shown.
$this->assertMediaAdded();
// Press the 'Remove button' and assert the user is sent back to the media
// library.
$page->pressButton('media-0-remove-button');
// Assert the remove message is shown.
$this->waitForText("The media item $png_image->filename has been removed.");
$this->assertNoMediaAdded();
$assert_session->elementExists('css', '.ui-dialog-titlebar-close')->click();
// Assert uploading multiple files.
$this->openMediaLibraryForField('field_unlimited_media');
$this->switchToMediaType('Three');
// Assert the existing items are remembered when adding and removing media.
$checkbox = $page->findField("Select $existing_media_name");
$checkbox->click();
// Assert we can add multiple files.
$this->assertTrue($assert_session->fieldExists('Add files')->hasAttribute('multiple'));
// Create a list of new files to upload.
$filenames = [];
$remote_paths = [];
foreach (range(1, 4) as $i) {
$path = $file_system->copy($png_image->uri, 'public://');
$filenames[] = $file_system->basename($path);
$remote_paths[] = $driver->uploadFileAndGetRemoteFilePath($file_system->realpath($path));
}
$page->findField('Add files')->setValue(implode("\n", $remote_paths));
// Assert the media item fields are shown and the vertical tabs are no
// longer shown.
$this->assertMediaAdded();
// Assert all files have been added.
$assert_session->fieldValueEquals('media[0][fields][name][0][value]', $filenames[0]);
$assert_session->fieldValueEquals('media[1][fields][name][0][value]', $filenames[1]);
$assert_session->fieldValueEquals('media[2][fields][name][0][value]', $filenames[2]);
$assert_session->fieldValueEquals('media[3][fields][name][0][value]', $filenames[3]);
// Assert the pre-selected items are shown.
$assert_session->checkboxChecked("Select $existing_media_name", $this->getSelectionArea());
// Set alt texts for items 1 and 2, leave the alt text empty for items 3
// and 4 to assert the field validation does not stop users from removing
// items.
$page->fillField('media[0][fields][field_media_test_image][0][alt]', $filenames[0]);
$page->fillField('media[1][fields][field_media_test_image][0][alt]', $filenames[1]);
// Assert the file is available in the file storage.
$files = $file_storage->loadByProperties(['filename' => $filenames[1]]);
$this->assertCount(1, $files);
$file_1_uri = reset($files)->getFileUri();
// Remove the second file and assert the focus is shifted to the container
// of the next media item and field values are still correct.
$page->pressButton('media-1-remove-button');
$this->assertJsCondition('jQuery("[data-media-library-added-delta=2]").is(":focus")');
$assert_session->pageTextContains('The media item ' . $filenames[1] . ' has been removed.');
// Assert the file was deleted.
$this->assertEmpty($file_storage->loadByProperties(['filename' => $filenames[1]]));
$this->assertFileDoesNotExist($file_1_uri);
// When a file is already in usage, it should not be deleted. To test,
// let's add a usage for $filenames[3] (now in the third position).
$files = $file_storage->loadByProperties(['filename' => $filenames[3]]);
$this->assertCount(1, $files);
$target_file = reset($files);
Media::create([
'bundle' => 'type_three',
'name' => 'Disturbing',
'field_media_test_image' => [
['target_id' => $target_file->id()],
],
])->save();
// Remove $filenames[3] (now in the third position) and assert the focus is
// shifted to the container of the previous media item and field values are
// still correct.
$page->pressButton('media-3-remove-button');
$this->assertTrue($assert_session->waitForText('The media item ' . $filenames[3] . ' has been removed.'));
// Assert the file was not deleted, due to being in use elsewhere.
$this->assertNotEmpty($file_storage->loadByProperties(['filename' => $filenames[3]]));
$this->assertFileExists($target_file->getFileUri());
// The second media item should be removed (this has the delta 1 since we
// start counting from 0).
$assert_session->elementNotExists('css', '[data-media-library-added-delta=1]');
$media_item_one = $assert_session->elementExists('css', '[data-media-library-added-delta=0]');
$assert_session->fieldValueEquals('Name', $filenames[0], $media_item_one);
$assert_session->fieldValueEquals('Alternative text', $filenames[0], $media_item_one);
$media_item_three = $assert_session->elementExists('css', '[data-media-library-added-delta=2]');
$assert_session->fieldValueEquals('Name', $filenames[2], $media_item_three);
$assert_session->fieldValueEquals('Alternative text', '', $media_item_three);
// Assert the pre-selected items are still shown.
$assert_session->checkboxChecked("Select $existing_media_name", $this->getSelectionArea());
// Remove the last file and assert the focus is shifted to the container
// of the first media item and field values are still correct.
$page->pressButton('media-2-remove-button');
$this->assertTrue($assert_session->waitForText('The media item ' . $filenames[2] . ' has been removed.'));
$this->assertJsCondition('jQuery("[data-media-library-added-delta=0]").is(":focus")');
$assert_session->pageTextContains('The media item ' . $filenames[2] . ' has been removed.');
$assert_session->elementNotExists('css', '[data-media-library-added-delta=1]');
$assert_session->elementNotExists('css', '[data-media-library-added-delta=2]');
$media_item_one = $assert_session->elementExists('css', '[data-media-library-added-delta=0]');
$assert_session->fieldValueEquals('Name', $filenames[0], $media_item_one);
$assert_session->fieldValueEquals('Alternative text', $filenames[0], $media_item_one);
}
}

View File

@@ -0,0 +1,163 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media_library\FunctionalJavascript;
/**
* Tests the views in the media library widget.
*
* @group media_library
*/
class WidgetViewsTest extends MediaLibraryTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Create a few example media items for use in selection.
$this->createMediaItems([
'type_one' => [
'Horse',
'Bear',
'Cat',
'Dog',
'Goat',
'Sheep',
'Pig',
'Cow',
'Chicken',
'Duck',
'Donkey',
'Llama',
'Mouse',
'Goldfish',
'Rabbit',
'Turkey',
'Dove',
'Giraffe',
'Tiger',
'Hamster',
'Parrot',
'Monkey',
'Koala',
'Panda',
'Kangaroo',
],
'type_two' => [
'Crocodile',
'Lizard',
'Snake',
'Turtle',
],
]);
// Create a user who can use the Media library.
$user = $this->drupalCreateUser([
'access content',
'create basic_page content',
'view media',
'create media',
]);
$this->drupalLogin($user);
}
/**
* Tests that the views in the Media library's widget work as expected.
*/
public function testWidgetViews(): void {
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
$this->drupalGet('node/add/basic_page');
$this->openMediaLibraryForField('field_unlimited_media');
// Assert the 'Apply filter' button is not moved to the button pane.
$button_pane = $assert_session->elementExists('css', '.ui-dialog-buttonpane');
$assert_session->buttonExists('Insert selected', $button_pane);
$assert_session->buttonNotExists('Apply filters', $button_pane);
// Assert the pager works as expected.
// An active pager item is not linked and contains "Page #" as text.
$assert_session->elementTextContains('css', '.js-media-library-view .js-pager__items > li:nth-of-type(1)', 'Page 1');
$assert_session->elementNotExists('css', '.js-media-library-view .js-pager__items > li:nth-of-type(1) a');
$assert_session->elementExists('css', '.js-media-library-view .js-pager__items > li:nth-of-type(2) a');
$this->assertCount(24, $this->getCheckboxes());
$page->clickLink('Next page');
$this->waitForElementTextContains('.js-media-library-view .js-pager__items > li:nth-of-type(2)', 'Page 2');
$assert_session->elementExists('css', '.js-media-library-view .js-pager__items > li:nth-of-type(1) a');
$assert_session->elementNotExists('css', '.js-media-library-view .js-pager__items > li:nth-of-type(2) a');
$this->assertCount(1, $this->getCheckboxes());
$page->clickLink('Previous page');
$this->waitForElementTextContains('.js-media-library-view .js-pager__items > li:nth-of-type(1)', 'Page 1');
$this->assertCount(24, $this->getCheckboxes());
$page->checkField('Select Bear');
$this->pressInsertSelected('Added one media item.');
$assert_session->pageTextContains('Bear');
$assert_session->pageTextNotContains('Cat');
$assert_session->pageTextNotContains('Turtle');
$this->openMediaLibraryForField('field_unlimited_media');
$this->switchToMediaLibraryTable();
// Assert the 'Apply filter' button is not moved to the button pane.
$assert_session->buttonExists('Insert selected', $button_pane);
$assert_session->buttonNotExists('Apply filters', $button_pane);
$assert_session->pageTextContains('Dog');
$assert_session->pageTextContains('Bear');
$assert_session->pageTextNotContains('Turtle');
// Assert the exposed filters can be applied and page is reset from second
// page.
$page->clickLink('Next page');
$this->waitForElementTextContains('.js-media-library-view .js-pager__items > li:nth-of-type(2)', 'Page 2');
$page->fillField('Name', 'Bear');
$page->pressButton('Apply filters');
$assert_session->assertWaitOnAjaxRequest();
$assert_session->pageTextNotContains('Dog');
$assert_session->pageTextContains('Bear');
$assert_session->pageTextNotContains('Turtle');
// Test clearing the filters.
$page->fillField('Name', '');
$page->pressButton('Apply filters');
$assert_session->waitForLink('Next page');
$page->clickLink('Next page');
$this->waitForElementTextContains('.js-media-library-view .js-pager__items > li:nth-of-type(2)', 'Page 2');
// Assert the exposed filters are persisted when changing display.
$page->fillField('Name', 'Dog');
$page->pressButton('Apply filters');
$assert_session->assertWaitOnAjaxRequest();
$assert_session->pageTextContains('Dog');
$assert_session->pageTextNotContains('Crocodile');
$assert_session->pageTextNotContains('Turtle');
$page->checkField('Select Dog');
$assert_session->linkExists('Table');
$this->switchToMediaLibraryGrid();
$this->assertSame('Dog', $page->findField('Name')->getValue());
$assert_session->pageTextContains('Dog');
$assert_session->pageTextNotContains('Crocodile');
$assert_session->pageTextNotContains('Turtle');
$assert_session->linkExists('Grid');
$this->switchToMediaLibraryTable();
// Select the item.
$this->pressInsertSelected('Added one media item.');
// Ensure that the selection completed successfully.
$assert_session->pageTextContains('Dog');
$assert_session->pageTextContains('Bear');
$assert_session->pageTextNotContains('Turtle');
}
}

View File

@@ -0,0 +1,156 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media_library\FunctionalJavascript;
use Drupal\Core\Url;
use Drupal\field_ui\FieldUI;
/**
* Tests the media library widget when no media types are available.
*
* @group media_library
*/
class WidgetWithoutTypesTest extends MediaLibraryTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['field_ui'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests that the widget works as expected when media types are deleted.
*/
public function testWidgetWithoutMediaTypes(): void {
$assert_session = $this->assertSession();
$user = $this->drupalCreateUser([
'access administration pages',
'access content',
'create basic_page content',
'create media',
'view media',
]);
$this->drupalLogin($user);
$default_message = 'There are no allowed media types configured for this field. Contact the site administrator.';
$this->drupalGet('node/add/basic_page');
// Assert a properly configured field does not show a message.
$assert_session->elementTextNotContains('css', '.field--name-field-twin-media', 'There are no allowed media types configured for this field.');
$assert_session->elementExists('css', '.js-media-library-open-button[name^="field_twin_media"]');
// Assert that the message is shown when the target_bundles setting for the
// entity reference field is an empty array. No types are allowed in this
// case.
$assert_session->elementTextContains('css', '.field--name-field-empty-types-media', $default_message);
$assert_session->elementNotExists('css', '.js-media-library-open-button[name^="field_empty_types_media"]');
// Assert that the message is not shown when the target_bundles setting for
// the entity reference field is null. All types are allowed in this case.
$assert_session->elementTextNotContains('css', '.field--name-field-null-types-media', 'There are no allowed media types configured for this field.');
$assert_session->elementExists('css', '.js-media-library-open-button[name^="field_null_types_media"]');
// Delete all media and media types.
$entity_type_manager = \Drupal::entityTypeManager();
$media_storage = $entity_type_manager->getStorage('media');
$media_type_storage = $entity_type_manager->getStorage('media_type');
$media_storage->delete($media_storage->loadMultiple());
$media_type_storage->delete($media_type_storage->loadMultiple());
// Visit a node create page.
$this->drupalGet('node/add/basic_page');
// Assert a properly configured field now shows a message.
$assert_session->elementTextContains('css', '.field--name-field-twin-media', $default_message);
$assert_session->elementNotExists('css', '.js-media-library-open-button[name^="field_twin_media"]');
// Assert that the message is shown when the target_bundles setting for the
// entity reference field is an empty array.
$assert_session->elementTextContains('css', '.field--name-field-empty-types-media', $default_message);
$assert_session->elementNotExists('css', '.js-media-library-open-button[name^="field_empty_types_media"]');
// Assert that the message is shown when the target_bundles setting for
// the entity reference field is null.
$assert_session->elementTextContains('css', '.field--name-field-null-types-media', $default_message);
$assert_session->elementNotExists('css', '.js-media-library-open-button[name^="field_null_types_media"]');
// Assert a different message is shown when the user is allowed to
// administer the fields.
$user = $this->drupalCreateUser([
'access administration pages',
'access content',
'create basic_page content',
'view media',
'administer node fields',
]);
$this->drupalLogin($user);
$route_bundle_params = FieldUI::getRouteBundleParameter(\Drupal::entityTypeManager()->getDefinition('node'), 'basic_page');
$field_twin_url = new Url('entity.field_config.node_field_edit_form', [
'field_config' => 'node.basic_page.field_twin_media',
] + $route_bundle_params);
$field_twin_message = 'There are no allowed media types configured for this field. <a href="' . $field_twin_url->toString() . '">Edit the field settings</a> to select the allowed media types.';
$field_empty_types_url = new Url('entity.field_config.node_field_edit_form', [
'field_config' => 'node.basic_page.field_empty_types_media',
] + $route_bundle_params);
$field_empty_types_message = 'There are no allowed media types configured for this field. <a href="' . $field_empty_types_url->toString() . '">Edit the field settings</a> to select the allowed media types.';
$field_null_types_url = new Url('entity.field_config.node_field_edit_form', [
'field_config' => 'node.basic_page.field_null_types_media',
] + $route_bundle_params);
$field_null_types_message = 'There are no allowed media types configured for this field. <a href="' . $field_null_types_url->toString() . '">Edit the field settings</a> to select the allowed media types.';
// Visit a node create page.
$this->drupalGet('node/add/basic_page');
// Assert a properly configured field still shows a message.
$assert_session->elementContains('css', '.field--name-field-twin-media', $field_twin_message);
$assert_session->elementNotExists('css', '.js-media-library-open-button[name^="field_twin_media"]');
// Assert that the message is shown when the target_bundles setting for the
// entity reference field is an empty array.
$assert_session->elementContains('css', '.field--name-field-empty-types-media', $field_empty_types_message);
$assert_session->elementNotExists('css', '.js-media-library-open-button[name^="field_empty_types_media"]');
// Assert that the message is shown when the target_bundles setting for the
// entity reference field is null.
$assert_session->elementContains('css', '.field--name-field-null-types-media', $field_null_types_message);
$assert_session->elementNotExists('css', '.js-media-library-open-button[name^="field_null_types_media"]');
// Assert the messages are also shown in the default value section of the
// field edit form.
$this->drupalGet($field_empty_types_url);
$assert_session->elementContains('css', '.field--name-field-empty-types-media', $field_empty_types_message);
$assert_session->elementNotExists('css', '.js-media-library-open-button[name^="field_empty_types_media"]');
$this->drupalGet($field_null_types_url);
$assert_session->elementContains('css', '.field--name-field-null-types-media', $field_null_types_message);
$assert_session->elementNotExists('css', '.js-media-library-open-button[name^="field_null_types_media"]');
// Uninstall the Field UI and check if the link is removed from the message.
\Drupal::service('module_installer')->uninstall(['field_ui']);
// Visit a node create page.
$this->drupalGet('node/add/basic_page');
$field_ui_uninstalled_message = 'There are no allowed media types configured for this field. Contact the site administrator.';
// Assert the link is now longer part of the message.
$assert_session->elementNotExists('named', ['link', 'Edit the field settings']);
// Assert a properly configured field still shows a message.
$assert_session->elementContains('css', '.field--name-field-twin-media', $field_ui_uninstalled_message);
$assert_session->elementNotExists('css', '.js-media-library-open-button[name^="field_twin_media"]');
// Assert that the message is shown when the target_bundles setting for the
// entity reference field is an empty array.
$assert_session->elementContains('css', '.field--name-field-empty-types-media', $field_ui_uninstalled_message);
$assert_session->elementNotExists('css', '.js-media-library-open-button[name^="field_empty_types_media"]');
// Assert that the message is shown when the target_bundles setting for the
// entity reference field is null.
$assert_session->elementContains('css', '.field--name-field-null-types-media', $field_ui_uninstalled_message);
$assert_session->elementNotExists('css', '.js-media-library-open-button[name^="field_null_types_media"]');
}
}

View File

@@ -0,0 +1,426 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media_library\Kernel;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Access\AccessResultReasonInterface;
use Drupal\entity_test\Entity\EntityTestBundle;
use Drupal\entity_test\Entity\EntityTestWithBundle;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\KernelTests\KernelTestBase;
use Drupal\media_library\MediaLibraryState;
use Drupal\Tests\media\Traits\MediaTypeCreationTrait;
use Drupal\Tests\user\Traits\UserCreationTrait;
use Drupal\views\Views;
/**
* Tests the media library access.
*
* @group media_library
*/
class MediaLibraryAccessTest extends KernelTestBase {
use UserCreationTrait;
use MediaTypeCreationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'entity_test',
'media',
'media_library',
'media_library_test',
'filter',
'file',
'field',
'image',
'system',
'views',
'user',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('user');
$this->installEntitySchema('file');
$this->installSchema('file', 'file_usage');
$this->installEntitySchema('entity_test_with_bundle');
$this->installEntitySchema('filter_format');
$this->installEntitySchema('media');
$this->installConfig([
'field',
'system',
'file',
'image',
'media',
'media_library',
]);
EntityTestBundle::create(['id' => 'test'])->save();
$field_storage = FieldStorageConfig::create([
'type' => 'entity_reference',
'field_name' => 'field_test_media',
'entity_type' => 'entity_test_with_bundle',
'settings' => [
'target_type' => 'media',
],
]);
$field_storage->save();
FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => 'test',
])->save();
// Create an account with special UID 1.
$this->createUser([]);
}
/**
* Tests that the field widget opener respects entity creation permissions.
*/
public function testFieldWidgetEntityCreateAccess(): void {
/** @var \Drupal\media_library\MediaLibraryUiBuilder $ui_builder */
$ui_builder = $this->container->get('media_library.ui_builder');
// Create a media library state to test access.
$state = MediaLibraryState::create('media_library.opener.field_widget', ['file', 'image'], 'file', 2, [
'entity_type_id' => 'entity_test_with_bundle',
'bundle' => 'test',
'field_name' => 'field_test_media',
]);
$access_result = $ui_builder->checkAccess($this->createUser(), $state);
$this->assertAccess($access_result, FALSE, "The following permissions are required: 'administer entity_test content' OR 'administer entity_test_with_bundle content' OR 'create test entity_test_with_bundle entities'.", [], ['url.query_args', 'user.permissions']);
// Create a user with the appropriate permissions and assert that access is
// granted.
$account = $this->createUser([
'create test entity_test_with_bundle entities',
'view media',
]);
$access_result = $ui_builder->checkAccess($account, $state);
$this->assertAccess($access_result, TRUE, NULL, Views::getView('media_library')->storage->getCacheTags(), ['url.query_args', 'user.permissions']);
}
/**
* @covers \Drupal\media_library\MediaLibraryEditorOpener::checkAccess
*
* @param bool $media_embed_enabled
* Whether to test with media_embed filter enabled on the text format.
* @param bool $can_use_format
* Whether the logged in user is allowed to use the text format.
*
* @dataProvider editorOpenerAccessProvider
*/
public function testEditorOpenerAccess($media_embed_enabled, $can_use_format): void {
$format = $this->container
->get('entity_type.manager')
->getStorage('filter_format')->create([
'format' => $this->randomMachineName(),
'name' => $this->randomString(),
'filters' => [
'media_embed' => ['status' => $media_embed_enabled],
],
]);
$format->save();
$permissions = [
'access media overview',
'view media',
];
if ($can_use_format) {
$permissions[] = $format->getPermissionName();
}
$state = MediaLibraryState::create(
'media_library.opener.editor',
['image'],
'image',
1,
['filter_format_id' => $format->id()]
);
$access_result = $this->container
->get('media_library.ui_builder')
->checkAccess($this->createUser($permissions), $state);
if ($media_embed_enabled && $can_use_format) {
$this->assertAccess($access_result, TRUE, NULL, Views::getView('media_library')->storage->getCacheTags(), ['user.permissions']);
}
else {
$this->assertAccess($access_result, FALSE, NULL, [], ['user.permissions']);
}
}
/**
* Data provider for ::testEditorOpenerAccess.
*/
public static function editorOpenerAccessProvider() {
return [
'media_embed filter enabled' => [
TRUE,
TRUE,
],
'media_embed filter disabled' => [
FALSE,
TRUE,
],
'media_embed filter enabled, user not allowed to use text format' => [
TRUE,
FALSE,
],
];
}
/**
* Tests that the field widget opener respects entity-specific access.
*/
public function testFieldWidgetEntityEditAccess(): void {
/** @var \Drupal\media_library\MediaLibraryUiBuilder $ui_builder */
$ui_builder = $this->container->get('media_library.ui_builder');
$forbidden_entity = EntityTestWithBundle::create([
'type' => 'test',
// This label will automatically cause an access denial.
// @see \Drupal\entity_test\EntityTestAccessControlHandler::checkAccess()
'name' => 'forbid_access',
]);
$forbidden_entity->save();
// Create a media library state to test access.
$state = MediaLibraryState::create('media_library.opener.field_widget', ['file', 'image'], 'file', 2, [
'entity_type_id' => $forbidden_entity->getEntityTypeId(),
'bundle' => $forbidden_entity->bundle(),
'field_name' => 'field_test_media',
'entity_id' => $forbidden_entity->id(),
]);
$access_result = $ui_builder->checkAccess($this->createUser(), $state);
$this->assertAccess($access_result, FALSE, NULL, [], ['url.query_args']);
$neutral_entity = EntityTestWithBundle::create([
'type' => 'test',
// This label will result in neutral access.
// @see \Drupal\entity_test\EntityTestAccessControlHandler::checkAccess()
'name' => $this->randomString(),
]);
$neutral_entity->save();
$parameters = $state->getOpenerParameters();
$parameters['entity_id'] = $neutral_entity->id();
$state = MediaLibraryState::create(
$state->getOpenerId(),
$state->getAllowedTypeIds(),
$state->getSelectedTypeId(),
$state->getAvailableSlots(),
$parameters
);
$access_result = $ui_builder->checkAccess($this->createUser(), $state);
$this->assertTrue($access_result->isNeutral());
$this->assertAccess($access_result, FALSE, NULL, [], ['url.query_args', 'user.permissions']);
// Give the user permission to edit the entity and assert that access is
// granted.
$account = $this->createUser([
'administer entity_test content',
'view media',
]);
$access_result = $ui_builder->checkAccess($account, $state);
$this->assertAccess($access_result, TRUE, NULL, Views::getView('media_library')->storage->getCacheTags(), ['url.query_args', 'user.permissions']);
}
/**
* Data provider for ::testFieldWidgetEntityFieldAccess().
*
* @return array[]
* Sets of arguments to pass to the test method.
*/
public static function providerFieldWidgetEntityFieldAccess(): array {
return [
['entity_reference'],
['entity_reference_subclass'],
];
}
/**
* Tests that the field widget opener respects entity field-level access.
*
* @param string $field_type
* The field type.
*
* @dataProvider providerFieldWidgetEntityFieldAccess
*/
public function testFieldWidgetEntityFieldAccess(string $field_type): void {
$field_storage = FieldStorageConfig::create([
'type' => $field_type,
'entity_type' => 'entity_test_with_bundle',
// The media_library_test module will deny access to this field.
// @see media_library_test_entity_field_access()
'field_name' => 'field_media_no_access',
'settings' => [
'target_type' => 'media',
],
]);
$field_storage->save();
FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => 'test',
])->save();
/** @var \Drupal\media_library\MediaLibraryUiBuilder $ui_builder */
$ui_builder = $this->container->get('media_library.ui_builder');
// Create an account with administrative access to the test entity type,
// so that we can be certain that field access is checked.
$account = $this->createUser(['administer entity_test content']);
// Test that access is denied even without an entity to work with.
$state = MediaLibraryState::create('media_library.opener.field_widget', ['file', 'image'], 'file', 2, [
'entity_type_id' => 'entity_test_with_bundle',
'bundle' => 'test',
'field_name' => $field_storage->getName(),
]);
$access_result = $ui_builder->checkAccess($account, $state);
$this->assertAccess($access_result, FALSE, 'Field access denied by test module', [], ['url.query_args', 'user.permissions']);
// Assert that field access is also checked with a real entity.
$entity = EntityTestWithBundle::create([
'type' => 'test',
'name' => $this->randomString(),
]);
$entity->save();
$parameters = $state->getOpenerParameters();
$parameters['entity_id'] = $entity->id();
$state = MediaLibraryState::create(
$state->getOpenerId(),
$state->getAllowedTypeIds(),
$state->getSelectedTypeId(),
$state->getAvailableSlots(),
$parameters
);
$access_result = $ui_builder->checkAccess($account, $state);
$this->assertAccess($access_result, FALSE, 'Field access denied by test module', [], ['url.query_args', 'user.permissions']);
}
/**
* Tests that media library access respects the media_library view.
*/
public function testViewAccess(): void {
/** @var \Drupal\media_library\MediaLibraryUiBuilder $ui_builder */
$ui_builder = $this->container->get('media_library.ui_builder');
// Create a media library state to test access.
$state = MediaLibraryState::create('media_library.opener.field_widget', ['file', 'image'], 'file', 2, [
'entity_type_id' => 'entity_test_with_bundle',
'bundle' => 'test',
'field_name' => 'field_test_media',
]);
// Create a clone of the view so we can reset the original later.
$view_original = clone Views::getView('media_library');
// Create our test users. Both have permission to create entity_test content
// so that we can specifically test Views-related access checking.
// @see ::testEntityCreateAccess()
$forbidden_account = $this->createUser([
'create test entity_test_with_bundle entities',
]);
$allowed_account = $this->createUser([
'create test entity_test_with_bundle entities',
'view media',
]);
// Assert the 'view media' permission is needed to access the library and
// validate the cache dependencies.
$access_result = $ui_builder->checkAccess($forbidden_account, $state);
$this->assertAccess($access_result, FALSE, "The 'view media' permission is required.", $view_original->storage->getCacheTags(), ['url.query_args', 'user.permissions']);
// Assert that the media library access is denied when the view widget
// display is deleted.
$view_storage = Views::getView('media_library')->storage;
$displays = $view_storage->get('display');
unset($displays['widget']);
$view_storage->set('display', $displays);
$view_storage->save();
$access_result = $ui_builder->checkAccess($allowed_account, $state);
$this->assertAccess($access_result, FALSE, 'The media library widget display does not exist.', $view_original->storage->getCacheTags());
// Restore the original view and assert that the media library controller
// works again.
$view_original->storage->save();
$access_result = $ui_builder->checkAccess($allowed_account, $state);
$this->assertAccess($access_result, TRUE, NULL, $view_original->storage->getCacheTags(), ['url.query_args', 'user.permissions']);
// Assert that the media library access is denied when the entire media
// library view is deleted.
Views::getView('media_library')->storage->delete();
$access_result = $ui_builder->checkAccess($allowed_account, $state);
$this->assertAccess($access_result, FALSE, 'The media library view does not exist.');
}
/**
* Tests that the media library respects arbitrary access to the add form.
*/
public function testAddFormAccess(): void {
// Access is denied if the media library is trying to create media whose
// type name is 'deny_access'. Also create a second media type that we *can*
// add, so we can be certain that the add form is otherwise visible.
// @see media_library_test_media_create_access()
$media_types = [
$this->createMediaType('image', ['id' => 'deny_access'])->id(),
$this->createMediaType('image')->id(),
];
$account = $this->createUser(['create media']);
$this->setCurrentUser($account);
/** @var \Drupal\media_library\MediaLibraryUiBuilder $ui_builder */
$ui_builder = $this->container->get('media_library.ui_builder');
$state = MediaLibraryState::create('test', $media_types, $media_types[0], 1);
$build = $ui_builder->buildUi($state);
$this->assertEmpty($build['content']['form']);
$state = MediaLibraryState::create('test', $media_types, $media_types[1], 1);
$build = $ui_builder->buildUi($state);
$this->assertNotEmpty($build['content']['form']);
}
/**
* Asserts various aspects of an access result.
*
* @param \Drupal\Core\Access\AccessResult $access_result
* The access result.
* @param bool $is_allowed
* The expected access status.
* @param string $expected_reason
* (optional) The expected reason attached to the access result.
* @param string[] $expected_cache_tags
* (optional) The expected cache tags attached to the access result.
* @param string[] $expected_cache_contexts
* (optional) The expected cache contexts attached to the access result.
*/
private function assertAccess(AccessResult $access_result, bool $is_allowed, ?string $expected_reason = NULL, array $expected_cache_tags = [], array $expected_cache_contexts = []): void {
$this->assertSame($is_allowed, $access_result->isAllowed());
if ($access_result instanceof AccessResultReasonInterface && isset($expected_reason)) {
$this->assertSame($expected_reason, $access_result->getReason());
}
$this->assertEqualsCanonicalizing($expected_cache_tags, $access_result->getCacheTags());
$this->assertEqualsCanonicalizing($expected_cache_contexts, $access_result->getCacheContexts());
}
}

View File

@@ -0,0 +1,158 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media_library\Kernel;
use Drupal\Core\Form\FormState;
use Drupal\KernelTests\KernelTestBase;
use Drupal\media_library\Form\FileUploadForm;
use Drupal\media_library\Form\OEmbedForm;
use Drupal\media_library\MediaLibraryState;
use Drupal\media_library_form_overwrite_test\Form\TestAddForm;
use Drupal\Tests\media\Traits\MediaTypeCreationTrait;
use Drupal\Tests\user\Traits\UserCreationTrait;
/**
* Tests the media library add form.
*
* @group media_library
*/
class MediaLibraryAddFormTest extends KernelTestBase {
use MediaTypeCreationTrait;
use UserCreationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'media',
'media_library',
'file',
'field',
'filter',
'image',
'system',
'views',
'user',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('user');
$this->installEntitySchema('file');
$this->installSchema('file', 'file_usage');
$this->installEntitySchema('media');
$this->installConfig([
'field',
'system',
'file',
'image',
'media',
'media_library',
]);
// Create an account with special UID 1.
$this->createUser([]);
$this->createMediaType('image', ['id' => 'image']);
$this->createMediaType('oembed:video', ['id' => 'remote_video']);
}
/**
* Tests the media library add form.
*/
public function testMediaTypeAddForm(): void {
$entity_type_manager = \Drupal::entityTypeManager();
$image = $entity_type_manager->getStorage('media_type')->load('image');
$remote_video = $entity_type_manager->getStorage('media_type')->load('remote_video');
$image_source_definition = $image->getSource()->getPluginDefinition();
$remote_video_source_definition = $remote_video->getSource()->getPluginDefinition();
// Assert the form class is added to the media source.
$this->assertSame(FileUploadForm::class, $image_source_definition['forms']['media_library_add']);
$this->assertSame(OEmbedForm::class, $remote_video_source_definition['forms']['media_library_add']);
// Assert the media library UI does not contains the add form when the user
// does not have access.
$this->assertEmpty($this->buildLibraryUi('image')['content']['form']);
$this->assertEmpty($this->buildLibraryUi('remote_video')['content']['form']);
// Create a user that has access to create the image media type but not the
// remote video media type.
$this->setCurrentUser($this->createUser([
'create image media',
]));
// Assert the media library UI only contains the add form for the image
// media type.
$this->assertSame('managed_file', $this->buildLibraryUi('image')['content']['form']['container']['upload']['#type']);
$this->assertEmpty($this->buildLibraryUi('remote_video')['content']['form']);
// Create a user that has access to create both media types.
$this->setCurrentUser($this->createUser([
'create image media',
'create remote_video media',
]));
// Assert the media library UI only contains the add form for both media
// types.
$this->assertSame('managed_file', $this->buildLibraryUi('image')['content']['form']['container']['upload']['#type']);
$this->assertSame('url', $this->buildLibraryUi('remote_video')['content']['form']['container']['url']['#type']);
}
/**
* Build the media library UI for a selected type.
*
* @param string $selected_type_id
* The selected media type ID.
*
* @return array
* The render array for the media library.
*/
protected function buildLibraryUi($selected_type_id) {
$state = MediaLibraryState::create('test', ['image', 'remote_video'], $selected_type_id, -1);
return \Drupal::service('media_library.ui_builder')->buildUi($state);
}
/**
* Tests the validation of the library state in the media library add form.
*/
public function testFormStateValidation(): void {
$form_state = new FormState();
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('The media library state is not present in the form state.');
\Drupal::formBuilder()->buildForm(FileUploadForm::class, $form_state);
}
/**
* Tests the validation of the selected type in the media library add form.
*/
public function testSelectedTypeValidation(): void {
$state = MediaLibraryState::create('test', ['image', 'remote_video', 'header_image'], 'header_image', -1);
$form_state = new FormState();
$form_state->set('media_library_state', $state);
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage("The 'header_image' media type does not exist.");
\Drupal::formBuilder()->buildForm(FileUploadForm::class, $form_state);
}
/**
* Tests overwriting of the add form.
*/
public function testDifferentAddForm(): void {
$this->enableModules(['media_library_form_overwrite_test']);
$entity_type_manager = \Drupal::entityTypeManager();
$image = $entity_type_manager->getStorage('media_type')->load('image');
$image_source_definition = $image->getSource()->getPluginDefinition();
// Assert the overwritten form class is set to the media source.
$this->assertSame(TestAddForm::class, $image_source_definition['forms']['media_library_add']);
}
}

View File

@@ -0,0 +1,400 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media_library\Kernel;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\KernelTests\KernelTestBase;
use Drupal\media_library\MediaLibraryState;
use Drupal\Tests\media\Traits\MediaTypeCreationTrait;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
/**
* Tests the media library state value object.
*
* @group media_library
*
* @coversDefaultClass \Drupal\media_library\MediaLibraryState
*/
class MediaLibraryStateTest extends KernelTestBase {
use MediaTypeCreationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'media',
'media_library',
'file',
'field',
'image',
'system',
'views',
'user',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('user');
$this->installEntitySchema('file');
$this->installSchema('file', 'file_usage');
$this->installEntitySchema('media');
$this->installConfig([
'field',
'system',
'file',
'image',
'media',
'media_library',
]);
// Create some media types to validate against.
$this->createMediaType('file', ['id' => 'document']);
$this->createMediaType('image', ['id' => 'image']);
$this->createMediaType('video_file', ['id' => 'video']);
}
/**
* Tests the media library state methods.
*/
public function testMethods(): void {
$opener_id = 'test';
$allowed_media_type_ids = ['document', 'image'];
$selected_media_type_id = 'image';
$remaining_slots = 2;
$state = MediaLibraryState::create($opener_id, $allowed_media_type_ids, $selected_media_type_id, $remaining_slots);
$this->assertSame($opener_id, $state->getOpenerId());
$this->assertSame($allowed_media_type_ids, $state->getAllowedTypeIds());
$this->assertSame($selected_media_type_id, $state->getSelectedTypeId());
$this->assertSame($remaining_slots, $state->getAvailableSlots());
$this->assertTrue($state->hasSlotsAvailable());
$state = MediaLibraryState::create($opener_id, $allowed_media_type_ids, $selected_media_type_id, 0);
$this->assertFalse($state->hasSlotsAvailable());
}
/**
* Tests the media library state creation.
*
* @param string $opener_id
* The opener ID.
* @param string[] $allowed_media_type_ids
* The allowed media type IDs.
* @param string $selected_type_id
* The selected media type ID.
* @param int $remaining_slots
* The number of remaining items the user is allowed to select or add in the
* library.
* @param string $exception_message
* The expected exception message.
*
* @covers ::create
* @dataProvider providerCreate
*/
public function testCreate($opener_id, array $allowed_media_type_ids, $selected_type_id, $remaining_slots, $exception_message = ''): void {
if ($exception_message) {
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage($exception_message);
}
$state = MediaLibraryState::create($opener_id, $allowed_media_type_ids, $selected_type_id, $remaining_slots);
$this->assertInstanceOf(MediaLibraryState::class, $state);
// Ensure that the state object carries cache metadata.
$this->assertInstanceOf(CacheableDependencyInterface::class, $state);
$this->assertSame(['url.query_args'], $state->getCacheContexts());
$this->assertSame(Cache::PERMANENT, $state->getCacheMaxAge());
}
/**
* Data provider for testCreate().
*
* @return array
* The data sets to test.
*/
public static function providerCreate() {
$test_data = [];
// Assert no exception is thrown when we add the parameters as expected.
$test_data['valid parameters'] = [
'test',
['document', 'image'],
'image',
2,
];
// Assert an exception is thrown when the opener ID parameter is empty.
$test_data['empty opener ID'] = [
'',
['document', 'image'],
'image',
2,
'The opener ID parameter is required and must be a string.',
];
// Assert an exception is thrown when the opener ID parameter is not a
// valid string.
$test_data['integer opener ID'] = [
1,
['document', 'image'],
'image',
2,
'The opener ID parameter is required and must be a string.',
];
$test_data['boolean opener ID'] = [
TRUE,
['document', 'image'],
'image',
2,
'The opener ID parameter is required and must be a string.',
];
$test_data['spaces opener ID'] = [
' ',
['document', 'image'],
'image',
2,
'The opener ID parameter is required and must be a string.',
];
// Assert an exception is thrown when the allowed types parameter is empty.
$test_data['empty allowed types'] = [
'test',
[],
'image',
2,
'The allowed types parameter is required and must be an array of strings.',
];
// It is not possible to assert a non-array allowed types parameter, since
// that would throw a TypeError which is not a subclass of Exception.
// Continue asserting an exception is thrown when the allowed types
// parameter contains elements that are not a valid string.
$test_data['integer in allowed types'] = [
'test',
[1, 'image'],
'image',
2,
'The allowed types parameter is required and must be an array of strings.',
];
$test_data['boolean in allowed types'] = [
'test',
[TRUE, 'image'],
'image',
2,
'The allowed types parameter is required and must be an array of strings.',
];
$test_data['spaces in allowed types'] = [
'test',
[' ', 'image'],
'image',
2,
'The allowed types parameter is required and must be an array of strings.',
];
// Assert an exception is thrown when the selected type parameter is empty.
$test_data['empty selected type'] = [
'test',
['document', 'image'],
'',
2,
'The selected type parameter is required and must be a string.',
];
// Assert an exception is thrown when the selected type parameter is not a
// valid string.
$test_data['numeric selected type'] = [
'test',
['document', 'image'],
1,
2,
'The selected type parameter is required and must be a string.',
];
$test_data['boolean selected type'] = [
'test',
['document', 'image'],
TRUE,
2,
'The selected type parameter is required and must be a string.',
];
$test_data['spaces selected type'] = [
'test',
['document', 'image'],
' ',
2,
'The selected type parameter is required and must be a string.',
];
// Assert an exception is thrown when the selected type parameter is not in
// the list of allowed types.
$test_data['non-present selected type'] = [
'test',
['document', 'image'],
'video',
2,
'The selected type parameter must be present in the list of allowed types.',
];
// Assert an exception is thrown when the remaining slots parameter is
// empty.
$test_data['empty remaining slots'] = [
'test',
['document', 'image'],
'image',
'',
'The remaining slots parameter is required and must be numeric.',
];
// Assert an exception is thrown when the remaining slots parameter is
// not numeric.
$test_data['string remaining slots'] = [
'test',
['document', 'image'],
'image',
'fail',
'The remaining slots parameter is required and must be numeric.',
];
$test_data['boolean remaining slots'] = [
'test',
['document', 'image'],
'image',
TRUE,
'The remaining slots parameter is required and must be numeric.',
];
return $test_data;
}
/**
* Tests the hash validation when the state is created from a request.
*
* @param array $query_overrides
* The query parameters to override.
* @param bool $exception_expected
* Whether an AccessDeniedHttpException is expected or not.
*
* @covers ::fromRequest
* @dataProvider providerFromRequest
*/
public function testFromRequest(array $query_overrides, $exception_expected): void {
// Override the query parameters and verify an exception is thrown when
// required state parameters are changed.
$query = MediaLibraryState::create('test', ['file', 'image'], 'image', 2)->all();
$query = array_merge($query, $query_overrides);
if ($exception_expected) {
$this->expectException(BadRequestHttpException::class);
$this->expectExceptionMessage("Invalid media library parameters specified.");
}
$state = MediaLibraryState::fromRequest(new Request($query));
$this->assertInstanceOf(MediaLibraryState::class, $state);
}
/**
* @covers ::fromRequest
*/
public function testFromRequestQueryLess(): void {
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('The opener ID parameter is required and must be a string.');
$state = MediaLibraryState::fromRequest(new Request());
$this->assertInstanceOf(MediaLibraryState::class, $state);
}
/**
* Data provider for testFromRequest().
*
* @return array
* The data sets to test.
*/
public static function providerFromRequest() {
$test_data = [];
// Assert no exception is thrown when we use valid state parameters.
$test_data['valid parameters'] = [
[],
FALSE,
];
// Assert no exception is thrown when we override all query parameters with
// the same data.
$test_data['changed with same values'] = [
[
'media_library_opener_id' => 'test',
'media_library_allowed_types' => ['file', 'image'],
'media_library_selected_type' => 'image',
'media_library_remaining' => 2,
],
FALSE,
];
// Assert an exception is thrown when we change the opener ID parameter.
$test_data['changed opener ID'] = [
['media_library_opener_id' => 'fail'],
TRUE,
];
// Assert an exception is thrown when we change the allowed types parameter.
$test_data['changed allowed types'] = [
['media_library_allowed_types' => ['audio', 'image']],
TRUE,
];
// Assert an exception is thrown when we change the selected type parameter.
$test_data['changed selected type'] = [
['media_library_selected_type' => 'file'],
TRUE,
];
// Assert an exception is thrown when we change the remaining slots
// parameter.
$test_data['changed remaining'] = [
['media_library_remaining' => 4],
TRUE,
];
// Assert an exception is thrown when we change the actual hash.
$test_data['changed hash'] = [
['hash' => 'fail'],
TRUE,
];
return $test_data;
}
/**
* @covers ::getOpenerParameters
*/
public function testOpenerParameters(): void {
$state = MediaLibraryState::create('test', ['file'], 'file', -1, [
'foo' => 'baz',
]);
$this->assertSame(['foo' => 'baz'], $state->getOpenerParameters());
}
/**
* Tests that hash is unaffected by allowed media type order.
*/
public function testHashUnaffectedByMediaTypeOrder(): void {
$state1 = MediaLibraryState::create('test', ['file', 'image'], 'image', 2);
$state2 = MediaLibraryState::create('test', ['image', 'file'], 'image', 2);
$this->assertSame($state1->getHash(), $state2->getHash());
}
/**
* Tests that hash is unaffected by opener parameter order.
*/
public function testHashUnaffectedByOpenerParamOrder(): void {
$state1 = MediaLibraryState::create('test', ['file'], 'file', -1, [
'foo' => 'baz',
'baz' => 'foo',
]);
$state2 = MediaLibraryState::create('test', ['file'], 'file', -1, [
'baz' => 'foo',
'foo' => 'baz',
]);
$this->assertSame($state1->getHash(), $state2->getHash());
}
}

View File

@@ -0,0 +1,171 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media_library\Kernel;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Form\FormState;
use Drupal\Core\Session\AccountInterface;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\entity_test\Entity\EntityTestRev;
use Drupal\KernelTests\KernelTestBase;
use Drupal\media\Entity\MediaType;
use Drupal\media_library\MediaLibraryState;
use Drupal\Tests\user\Traits\UserCreationTrait;
/**
* Tests the media library widget.
*
* @coversDefaultClass \Drupal\media_library\Plugin\Field\FieldWidget\MediaLibraryWidget
* @group media_library
*/
class MediaLibraryWidgetTest extends KernelTestBase {
use UserCreationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'media',
'media_library',
'field',
'filter',
'image',
'system',
'views',
'user',
'entity_test',
];
/**
* An admin user.
*
* @var \Drupal\user\Entity\User
*/
protected $adminUser;
/**
* The base field definition.
*
* @var \Drupal\Core\Field\BaseFieldDefinition
*/
protected BaseFieldDefinition $baseField;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->baseField = BaseFieldDefinition::create('entity_reference')
->setName('media')
->setSetting('target_type', 'media')
->setSetting('handler_settings', ['target_bundles' => ['test_type' => 'test_type']]);
$this->container->get('state')->set('entity_test.additional_base_field_definitions', [
'media' => $this->baseField,
]);
$this->container->get('state')->set('entity_test_rev.additional_base_field_definitions', [
'media' => $this->baseField,
]);
$this->installEntitySchema('entity_test');
$this->installEntitySchema('entity_test_rev');
$this->installEntitySchema('user');
$this->installConfig([
'system',
'image',
'media',
'media_library',
]);
MediaType::create([
'id' => 'test_type',
'label' => 'Test type',
'source' => 'image',
])->save();
// Create user 1 so the test user doesn't bypass access control.
$this->createUser();
$this->adminUser = $this->createUser([
'administer entity_test content',
'view media',
]);
}
/**
* Test the media library widget access.
*/
public function testWidgetAccess(): void {
$entity = EntityTest::create([
'name' => 'sample entity',
]);
$entity->save();
$element = $this->buildWidgetForm($entity);
$this->assertMediaLibraryStateAccess(TRUE, $this->adminUser, $element['open_button']['#media_library_state']);
}
/**
* Test the media library widget access with a revisionable entity type.
*/
public function testRevisionableWidgetAccess(): void {
$allowed_revision = EntityTestRev::create([
'name' => 'allowed_access',
]);
$allowed_revision->save();
$denied_revision = clone $allowed_revision;
$denied_revision->setNewRevision();
$denied_revision->name = 'forbid_access';
$denied_revision->save();
$element = $this->buildWidgetForm($allowed_revision);
$this->assertMediaLibraryStateAccess(TRUE, $this->adminUser, $element['open_button']['#media_library_state']);
$element = $this->buildWidgetForm($denied_revision);
$this->assertMediaLibraryStateAccess(FALSE, $this->adminUser, $element['open_button']['#media_library_state']);
}
/**
* Assert if the given user has access to the given state.
*
* @param bool $access
* The access result to assert.
* @param \Drupal\Core\Session\AccountInterface $user
* The user account.
* @param \Drupal\media_library\MediaLibraryState $state
* The media library state.
*
* @throws \Exception
*
* @internal
*/
protected function assertMediaLibraryStateAccess(bool $access, AccountInterface $user, MediaLibraryState $state): void {
$ui_builder = $this->container->get('media_library.ui_builder');
$access_result = $ui_builder->checkAccess($user, $state);
$this->assertEquals($access, $access_result->isAllowed());
}
/**
* Build the media library widget form.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to build the form for.
*
* @return array
* A built form array of the media library widget.
*/
protected function buildWidgetForm($entity) {
$form = [
'#parents' => [],
];
return $this->container->get('plugin.manager.field.widget')->createInstance('media_library_widget', [
'field_definition' => $this->baseField,
'settings' => [],
'third_party_settings' => [],
])->formElement($entity->media, 0, ['#description' => ''], $form, new FormState());
}
}

View File

@@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media_library\Unit;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\media_library\Plugin\views\field\MediaLibrarySelectForm;
use Drupal\Tests\UnitTestCase;
use Drupal\views\Entity\View;
use Drupal\views\Plugin\views\display\DefaultDisplay;
use Drupal\views\Plugin\ViewsPluginManager;
use Drupal\views\ResultRow;
use Drupal\views\ViewExecutable;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpFoundation\InputBag;
use Symfony\Component\HttpFoundation\Request;
/**
* @coversDefaultClass \Drupal\media_library\Plugin\views\field\MediaLibrarySelectForm
* @group media_library
*/
class MediaLibrarySelectFormTest extends UnitTestCase {
/**
* {@inheritdoc}
*/
protected function tearDown(): void {
parent::tearDown();
$container = new ContainerBuilder();
\Drupal::setContainer($container);
}
/**
* @covers ::viewsForm
*/
public function testViewsForm(): void {
$row = new ResultRow();
$field = $this->getMockBuilder(MediaLibrarySelectForm::class)
->onlyMethods(['getEntity'])
->disableOriginalConstructor()
->getMock();
$field->expects($this->any())
->method('getEntity')
->willReturn(NULL);
$container = new ContainerBuilder();
$container->set('string_translation', $this->createMock(TranslationInterface::class));
\Drupal::setContainer($container);
$request = $this->getMockBuilder(Request::class)
->disableOriginalConstructor()
->getMock();
$request->query = new InputBag();
$view = $this->getMockBuilder(ViewExecutable::class)
->onlyMethods(['getRequest', 'initStyle', 'getDisplay'])
->disableOriginalConstructor()
->getMock();
$view->expects($this->any())
->method('getRequest')
->willReturn($request);
$view->expects($this->any())
->method('initStyle')
->willReturn(TRUE);
$display = $this->getMockBuilder(DefaultDisplay::class)
->disableOriginalConstructor()
->getMock();
$display->display['id'] = 'foo';
$view->expects($this->any())
->method('getDisplay')
->willReturn($display);
$view_entity = $this->getMockBuilder(View::class)
->disableOriginalConstructor()
->getMock();
$view_entity->expects($this->any())
->method('get')
->willReturn([]);
$view->storage = $view_entity;
$display_manager = $this->getMockBuilder(ViewsPluginManager::class)
->disableOriginalConstructor()
->getMock();
$display = $this->getMockBuilder(DefaultDisplay::class)
->disableOriginalConstructor()
->getMock();
$display_manager->expects($this->any())
->method('createInstance')
->willReturn($display);
$container->set('plugin.manager.views.display', $display_manager);
\Drupal::setContainer($container);
$form_state = $this->createMock(FormStateInterface::class);
$view->result = [$row];
$field->view = $view;
$field->options = ['id' => 'bar'];
$form = [];
$field->viewsForm($form, $form_state);
$this->assertNotEmpty($form);
$this->assertNotEmpty($field->view->result);
$this->assertIsArray($form[$field->options['id']][0]);
$this->assertEmpty($form[$field->options['id']][0]);
}
}