Initial Drupal 11 with DDEV setup

This commit is contained in:
gluebox
2025-10-08 11:39:17 -04:00
commit 89ef74b305
25344 changed files with 2599172 additions and 0 deletions

View File

@ -0,0 +1,130 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional\FieldFormatter;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\file\Entity\File;
use Drupal\media\Entity\Media;
use Drupal\Tests\media\Functional\MediaFunctionalTestBase;
use Drupal\Tests\TestFileCreationTrait;
/**
* @covers \Drupal\media\Plugin\Field\FieldFormatter\MediaThumbnailFormatter
*
* @group media
*/
class MediaThumbnailFormatterTest extends MediaFunctionalTestBase {
use TestFileCreationTrait;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests the media thumbnail field formatter.
*/
public function testRender(): void {
$this->drupalLogin($this->adminUser);
// Create an image media type for testing the formatter.
$this->createMediaType('image', ['id' => 'image']);
// Create an article content type.
$this->drupalCreateContentType([
'type' => 'article',
'name' => 'Article',
]);
// Creates an entity reference field for media.
$field_storage = FieldStorageConfig::create([
'field_name' => 'field_media_reference',
'type' => 'entity_reference',
'entity_type' => 'node',
'cardinality' => 1,
'settings' => [
'target_type' => 'media',
],
]);
$field_storage->save();
FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => 'article',
'label' => 'Reference media',
'translatable' => FALSE,
])->save();
// Alter the form display.
$this->container->get('entity_display.repository')
->getFormDisplay('node', 'article')
->setComponent('field_media_reference', [
'type' => 'entity_reference_autocomplete',
])
->save();
// Change the image thumbnail to point into the media.
$this->changeMediaReferenceFieldLinkType('media');
// Create and upload a file to the media.
$file = File::create([
'uri' => current($this->getTestFiles('image'))->uri,
]);
$file->save();
$mediaImage = Media::create([
'bundle' => 'image',
'name' => 'Test image',
'field_media_image' => $file->id(),
]);
$mediaImage->save();
// Save the article node.
$title = $this->randomMachineName();
$edit = [
'title[0][value]' => $title,
];
$edit['field_media_reference[0][target_id]'] = $mediaImage->getName();
$this->drupalGet('node/add/article');
$this->submitForm($edit, 'Save');
// Validate the image being loaded with the media reference.
$this->assertSession()->responseContains('<a href="' . $mediaImage->toUrl('edit-form')->toString());
// Retrieve the created node.
$node = $this->drupalGetNodeByTitle($title);
$nid = $node->id();
// Change the image thumbnail to point into the content node.
$this->changeMediaReferenceFieldLinkType('content');
$this->drupalGet('node/' . $nid);
// Validate image being loaded with the content on the link.
$this->assertSession()->responseContains('<a href="' . $node->toUrl()->toString());
$this->assertSession()->responseContains('loading="eager"');
}
/**
* Helper function to change field display.
*
* @param string $type
* Image link type.
*/
private function changeMediaReferenceFieldLinkType(string $type): void {
// Change the display to use the media thumbnail formatter with image link.
$this->container->get('entity_display.repository')
->getViewDisplay('node', 'article', 'default')
->setComponent('field_media_reference', [
'type' => 'media_thumbnail',
'settings' => [
'image_link' => $type,
'image_style' => '',
'image_loading' => ['attribute' => 'eager'],
],
])
->save();
}
}

View File

@ -0,0 +1,247 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional\FieldFormatter;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\media\Entity\Media;
use Drupal\media_test_oembed\Controller\ResourceController;
use Drupal\media_test_oembed\UrlResolver;
use Drupal\Tests\media\Functional\MediaFunctionalTestBase;
use Drupal\Tests\media\Traits\OEmbedTestTrait;
// cspell:ignore Schipulcon
/**
* @covers \Drupal\media\Plugin\Field\FieldFormatter\OEmbedFormatter
*
* @group media
* @group #slow
*/
class OEmbedFormatterTest extends MediaFunctionalTestBase {
use OEmbedTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'field_ui',
'link',
'media_test_oembed',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->lockHttpClientToFixtures();
\Drupal::configFactory()
->getEditable('media.settings')
->set('standalone_url', TRUE)
->save(TRUE);
$this->container->get('router.builder')->rebuild();
}
/**
* Data provider for testRender().
*
* @see ::testRender()
*
* @return array
* An array of test data.
*/
public static function providerRender() {
return [
'Vimeo video' => [
'https://vimeo.com/7073899',
'video_vimeo.json',
[],
[
'iframe' => [
'src' => '/media/oembed?url=https%3A//vimeo.com/7073899',
'width' => '480',
'height' => '360',
'title' => 'Drupal Rap Video - Schipulcon09',
'loading' => 'lazy',
// cSpell:disable-next-line
'allowtransparency' => NULL,
'frameborder' => NULL,
],
],
'self_closing' => TRUE,
],
'Vimeo video, resized' => [
'https://vimeo.com/7073899',
'video_vimeo-resized.json',
['max_width' => '100', 'max_height' => '100'],
[
'iframe' => [
'src' => '/media/oembed?url=https%3A//vimeo.com/7073899&max_width=100&max_height=100',
'width' => '100',
'height' => '67',
'title' => 'Drupal Rap Video - Schipulcon09',
'loading' => 'lazy',
],
],
'self_closing' => TRUE,
],
'Vimeo video, no title' => [
'https://vimeo.com/7073899',
'video_vimeo-no-title.json',
[],
[
'iframe' => [
'src' => '/media/oembed?url=https%3A//vimeo.com/7073899',
'width' => '480',
'height' => '360',
'title' => NULL,
'loading' => 'lazy',
],
],
'self_closing' => TRUE,
],
'tweet' => [
'https://twitter.com/drupaldevdays/status/935643039741202432',
'rich_twitter.json',
[
// The tweet resource does not specify a height, so the formatter
// should default to the configured maximum height.
'max_height' => 360,
'loading' => ['attribute' => 'eager'],
],
[
'iframe' => [
'src' => '/media/oembed?url=https%3A//twitter.com/drupaldevdays/status/935643039741202432',
'width' => '550',
'height' => '360',
'loading' => 'eager',
],
],
'self_closing' => TRUE,
],
'Flickr photo' => [
'https://www.flickr.com/photos/amazeelabs/26497866357',
'photo_flickr.json',
[],
[
'img' => [
'src' => '/core/misc/druplicon.png',
'width' => '88',
'height' => '100',
'loading' => 'lazy',
],
],
'self_closing' => FALSE,
],
'Flickr photo (no dimensions)' => [
'https://www.flickr.com/photos/amazeelabs/26497866357',
'photo_flickr_no_dimensions.json',
[],
[
'img' => [
'src' => '/core/misc/druplicon.png',
'loading' => 'lazy',
],
],
'self_closing' => FALSE,
],
];
}
/**
* Tests that oEmbed media types' display can be configured correctly.
*/
public function testDisplayConfiguration(): void {
$account = $this->drupalCreateUser(['administer media display']);
$this->drupalLogin($account);
$media_type = $this->createMediaType('oembed:video');
$this->drupalGet('/admin/structure/media/manage/' . $media_type->id() . '/display');
$assert = $this->assertSession();
$assert->statusCodeEquals(200);
// Test that the formatter doesn't try to check applicability for fields
// which do not have a specific target bundle.
// @see https://www.drupal.org/project/drupal/issues/2976795.
$assert->pageTextNotContains('Can only flip STRING and INTEGER values!');
}
/**
* Tests the oEmbed field formatter.
*
* @param string $url
* The canonical URL of the media asset to test.
* @param string $resource_url
* The oEmbed resource URL of the media asset to test.
* @param array $formatter_settings
* Settings for the oEmbed field formatter.
* @param array $selectors
* An array of arrays. Each key is a CSS selector targeting an element in
* the rendered output, and each value is an array of attributes, keyed by
* name, that the element is expected to have.
* @param bool $self_closing
* Indicator if the HTML element is self closing i.e. <p/> vs <p></p>.
*
* @dataProvider providerRender
*/
public function testRender($url, $resource_url, array $formatter_settings, array $selectors, bool $self_closing): void {
$account = $this->drupalCreateUser(['view media']);
$this->drupalLogin($account);
$media_type = $this->createMediaType('oembed:video');
$source = $media_type->getSource();
$source_field = $source->getSourceFieldDefinition($media_type);
EntityViewDisplay::create([
'targetEntityType' => 'media',
'bundle' => $media_type->id(),
'mode' => 'full',
'status' => TRUE,
])->removeComponent('thumbnail')
->setComponent($source_field->getName(), [
'type' => 'oembed',
'settings' => $formatter_settings,
])
->save();
$this->hijackProviderEndpoints();
ResourceController::setResourceUrl($url, $this->getFixturesDirectory() . '/' . $resource_url);
UrlResolver::setEndpointUrl($url, $resource_url);
$entity = Media::create([
'bundle' => $media_type->id(),
$source_field->getName() => $url,
]);
$entity->save();
$this->drupalGet($entity->toUrl());
$assert = $this->assertSession();
$assert->statusCodeEquals(200);
foreach ($selectors as $selector => $attributes) {
$element = $assert->elementExists('css', $selector);
if ($self_closing) {
self::assertStringContainsString("</$selector", $element->getParent()->getHtml());
}
foreach ($attributes as $attribute => $value) {
if (isset($value)) {
$this->assertStringContainsString($value, $element->getAttribute($attribute));
}
else {
$this->assertFalse($element->hasAttribute($attribute));
}
}
}
}
}

View File

@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional\FieldWidget;
use Drupal\field\Entity\FieldConfig;
use Drupal\Tests\media\Functional\MediaFunctionalTestBase;
/**
* @covers \Drupal\media\Plugin\Field\FieldWidget\OEmbedWidget
*
* @group media
*/
class OEmbedFieldWidgetTest extends MediaFunctionalTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests that the oEmbed field widget shows the configured help text.
*/
public function testFieldWidgetHelpText(): void {
$account = $this->drupalCreateUser(['create media']);
$this->drupalLogin($account);
$media_type = $this->createMediaType('oembed:video');
$source_field = $media_type->getSource()
->getSourceFieldDefinition($media_type)
->getName();
/** @var \Drupal\field\Entity\FieldConfig $field */
$field = FieldConfig::loadByName('media', $media_type->id(), $source_field);
$field->setDescription('This is help text for oEmbed field.')
->save();
$this->drupalGet('media/add/' . $media_type->id());
$assert_session = $this->assertSession();
$assert_session->pageTextContains('This is help text for oEmbed field.');
$assert_session->pageTextContains('You can link to media from the following services: YouTube, Vimeo');
}
}

View File

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

View File

@ -0,0 +1,451 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\media\Entity\Media;
use Drupal\Tests\system\Functional\Cache\AssertPageCacheContextsAndTagsTrait;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
/**
* Basic access tests for Media.
*
* @group media
*/
class MediaAccessTest extends MediaFunctionalTestBase {
use AssertPageCacheContextsAndTagsTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'block',
'media_test_source',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// This is needed to provide the user cache context for a below assertion.
$this->drupalPlaceBlock('local_tasks_block');
}
/**
* Tests some access control functionality.
*/
public function testMediaAccess(): void {
$assert_session = $this->assertSession();
$media_type = $this->createMediaType('test');
\Drupal::configFactory()
->getEditable('media.settings')
->set('standalone_url', TRUE)
->save(TRUE);
$this->container->get('router.builder')->rebuild();
// Create media.
$media = Media::create([
'bundle' => $media_type->id(),
'name' => 'Unnamed',
]);
$media->save();
$user_media = Media::create([
'bundle' => $media_type->id(),
'name' => 'Unnamed',
'uid' => $this->nonAdminUser->id(),
]);
$user_media->save();
// We are logged in as admin, so test 'administer media' permission.
$this->drupalGet('media/add/' . $media_type->id());
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(200);
$this->drupalGet('media/' . $user_media->id());
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(200);
$this->drupalGet('media/' . $user_media->id() . '/edit');
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(200);
$this->drupalGet('media/' . $user_media->id() . '/delete');
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(200);
$this->drupalLogin($this->nonAdminUser);
/** @var \Drupal\user\RoleInterface $role */
$role = Role::load(RoleInterface::AUTHENTICATED_ID);
user_role_revoke_permissions($role->id(), ['view media']);
// Test 'create BUNDLE media' permission.
$this->drupalGet('media/add/' . $media_type->id());
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(403);
$permissions = ['create ' . $media_type->id() . ' media'];
$this->grantPermissions($role, $permissions);
$this->drupalGet('media/add/' . $media_type->id());
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(200);
user_role_revoke_permissions($role->id(), $permissions);
$role = Role::load(RoleInterface::AUTHENTICATED_ID);
// Verify the author can not view the unpublished media item without
// 'view own unpublished media' permission.
$this->grantPermissions($role, ['view media']);
$this->drupalGet('media/' . $user_media->id());
$this->assertNoCacheContext('user');
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(200);
$previous_revision = $user_media->getLoadedRevisionId();
$user_media->setUnpublished()->setNewRevision();
$user_media->save();
$this->drupalGet('media/' . $user_media->id());
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(403);
$access_result = $user_media->access('view', NULL, TRUE);
$this->assertSame("The user must be the owner and the 'view own unpublished media' permission is required when the media item is unpublished.", $access_result->getReason());
$this->grantPermissions($role, ['view own unpublished media']);
$this->drupalGet('media/' . $user_media->id());
$this->assertCacheContext('user');
$assert_session->statusCodeEquals(200);
// Test revision access - logged-in user.
$this->grantPermissions($role, ['view all media revisions']);
$this->drupalGet('media/' . $user_media->id() . '/revisions');
$this->assertCacheContext('user');
$assert_session->statusCodeEquals(200);
$this->drupalGet('media/' . $user_media->id() . '/revisions/' . $user_media->getRevisionId() . '/view');
$this->assertCacheContext('user');
$assert_session->statusCodeEquals(200);
$this->drupalGet('media/' . $user_media->id() . '/revisions/' . $previous_revision . '/view');
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(200);
$role->revokePermission('view own unpublished media')->save();
$this->drupalGet('media/' . $user_media->id() . '/revisions/' . $user_media->getRevisionId() . '/view');
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(403);
$user_media->setPublished()->setNewRevision();
$user_media->save();
// Revision access - logged-out user.
$this->drupalLogout();
$this->drupalGet('media/' . $user_media->id() . '/revisions');
$assert_session->statusCodeEquals(403);
$this->drupalGet('media/' . $user_media->id() . '/revisions/' . $user_media->getRevisionId() . '/view');
$assert_session->statusCodeEquals(403);
$this->drupalGet('media/' . $user_media->id() . '/revisions/' . $previous_revision . '/view');
$assert_session->statusCodeEquals(403);
// Reverse revision access testing changes.
$role
->revokePermission('view all media revisions')
->grantPermission('view own unpublished media')
->save();
$user_media->setPublished()->setNewRevision();
$user_media->save();
$this->drupalLogin($this->nonAdminUser);
// Test 'create media' permission.
$this->drupalGet('media/add/' . $media_type->id());
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(403);
$permissions = ['create media'];
$this->grantPermissions($role, $permissions);
$this->drupalGet('media/add/' . $media_type->id());
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(200);
user_role_revoke_permissions($role->id(), $permissions);
$role = Role::load(RoleInterface::AUTHENTICATED_ID);
// Test 'edit own BUNDLE media' and 'delete own BUNDLE media' permissions.
$this->drupalGet('media/' . $user_media->id() . '/edit');
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(403);
$this->drupalGet('media/' . $user_media->id() . '/delete');
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(403);
$permissions = [
'edit own ' . $user_media->bundle() . ' media',
'delete own ' . $user_media->bundle() . ' media',
];
$this->grantPermissions($role, $permissions);
$this->drupalGet('media/' . $user_media->id() . '/edit');
$this->assertCacheContext('user');
$assert_session->statusCodeEquals(200);
$this->drupalGet('media/' . $user_media->id() . '/delete');
$this->assertCacheContext('user');
$assert_session->statusCodeEquals(200);
user_role_revoke_permissions($role->id(), $permissions);
$role = Role::load(RoleInterface::AUTHENTICATED_ID);
// Test 'edit any BUNDLE media' and 'delete any BUNDLE media' permissions.
$this->drupalGet('media/' . $media->id() . '/edit');
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(403);
$this->drupalGet('media/' . $media->id() . '/delete');
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(403);
$permissions = [
'edit any ' . $media->bundle() . ' media',
'delete any ' . $media->bundle() . ' media',
];
$this->grantPermissions($role, $permissions);
$this->drupalGet('media/' . $media->id() . '/edit');
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(200);
$this->drupalGet('media/' . $media->id() . '/delete');
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(200);
// Test the 'access media overview' permission.
$this->grantPermissions($role, ['access content overview']);
$this->drupalGet('admin/content');
$assert_session->linkByHrefNotExists('/admin/content/media');
$this->assertCacheContext('user');
// Create a new role, which implicitly checks if the permission exists.
$mediaOverviewRole = $this->createRole(['access content overview', 'access media overview']);
$this->nonAdminUser->addRole($mediaOverviewRole)->save();
$this->grantPermissions($role, ['access user profiles']);
$this->drupalGet('admin/content');
$assert_session->linkByHrefExists('/admin/content/media');
$this->clickLink('Media');
$this->assertCacheContext('user');
$assert_session->statusCodeEquals(200);
$assert_session->elementExists('css', '.views-element-container');
// First row of the View contains media created by admin user.
$assert_session->elementTextEquals('xpath', '//div[@class="views-element-container"]//tbody/tr[1]/td[contains(@class, "views-field-uid")]/a', $this->adminUser->getDisplayName());
$assert_session->elementTextEquals('xpath', "//div[@class='views-element-container']//tbody/tr[1]/td[contains(@class, 'views-field-name')]/a[contains(@href, '/media/{$media->id()}')]", 'Unnamed');
// Second row of the View contains media created by non-admin user.
$assert_session->elementTextEquals('xpath', '//div[@class="views-element-container"]//tbody/tr[2]/td[contains(@class, "views-field-uid")]/a', $this->nonAdminUser->getDisplayName());
$assert_session->elementTextEquals('xpath', "//div[@class='views-element-container']//tbody/tr[2]/td[contains(@class, 'views-field-name')]/a[contains(@href, '/media/{$user_media->id()}')]", 'Unnamed');
}
/**
* Tests view access control on the canonical page.
*/
public function testCanonicalMediaAccess(): void {
$media_type = $this->createMediaType('test');
$assert_session = $this->assertSession();
\Drupal::configFactory()
->getEditable('media.settings')
->set('standalone_url', TRUE)
->save(TRUE);
$this->container->get('router.builder')->rebuild();
// Create media.
$media = Media::create([
'bundle' => $media_type->id(),
'name' => 'Unnamed',
]);
$media->save();
$user_media = Media::create([
'bundle' => $media_type->id(),
'name' => 'Unnamed',
'uid' => $this->nonAdminUser->id(),
]);
$user_media->save();
$this->drupalLogin($this->nonAdminUser);
/** @var \Drupal\user\RoleInterface $role */
$role = Role::load(RoleInterface::AUTHENTICATED_ID);
user_role_revoke_permissions($role->id(), ['view media']);
$this->drupalGet('media/' . $media->id());
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(403);
$access_result = $media->access('view', NULL, TRUE);
$this->assertSame("The 'view media' permission is required when the media item is published.", $access_result->getReason());
$this->grantPermissions($role, ['view media']);
$this->drupalGet('media/' . $media->id());
$this->assertCacheContext('user.permissions');
$assert_session->statusCodeEquals(200);
}
/**
* Tests unpublished media access.
*/
public function testUnpublishedMediaUserAccess(): void {
\Drupal::configFactory()
->getEditable('media.settings')
->set('standalone_url', TRUE)
->save(TRUE);
$this->container->get('router.builder')->rebuild();
$assert_session = $this->assertSession();
$media_type = $this->createMediaType('test');
$permissions = [
'view media',
'view own unpublished media',
];
$user_one = $this->drupalCreateUser($permissions);
$user_two = $this->drupalCreateUser($permissions);
// Create media as user one.
$user_media = Media::create([
'bundle' => $media_type->id(),
'name' => 'Unnamed',
'uid' => $user_one->id(),
]);
$user_media->setUnpublished()->save();
// Make sure user two can't access unpublished media.
$this->drupalLogin($user_two);
$this->drupalGet('media/' . $user_media->id());
$assert_session->statusCodeEquals(403);
$this->assertCacheContext('user');
$this->drupalLogout();
// Make sure user one can access own unpublished media.
$this->drupalLogin($user_one);
$this->drupalGet('media/' . $user_media->id());
$assert_session->statusCodeEquals(200);
$this->assertCacheContext('user');
}
/**
* Tests media access of anonymous user.
*/
public function testMediaAnonymousUserAccess(): void {
\Drupal::configFactory()
->getEditable('media.settings')
->set('standalone_url', TRUE)
->save(TRUE);
$this->container->get('router.builder')->rebuild();
$assert_session = $this->assertSession();
$media_type = $this->createMediaType('test');
// Create media as anonymous user.
$user_media = Media::create([
'bundle' => $media_type->id(),
'name' => 'Unnamed',
'uid' => 0,
]);
$user_media->save();
$role = Role::load(RoleInterface::ANONYMOUS_ID);
$this->grantPermissions($role, ['view media', 'view own unpublished media']);
$this->drupalLogout();
// Make sure anonymous users can access published media.
$user_media->setPublished()->save();
$this->drupalGet('media/' . $user_media->id());
$assert_session->statusCodeEquals(200);
// Make sure anonymous users can not access unpublished media
// even though role has 'view own unpublished media' permission.
$user_media->setUnpublished()->save();
$this->drupalGet('media/' . $user_media->id());
$assert_session->statusCodeEquals(403);
$this->assertCacheContext('user');
}
/**
* Tests access for embedded medias.
*/
public function testReferencedRendering(): void {
\Drupal::configFactory()
->getEditable('media.settings')
->set('standalone_url', TRUE)
->save(TRUE);
$this->container->get('router.builder')->rebuild();
// Create a media type and an entity reference to itself.
$media_type = $this->createMediaType('test');
FieldStorageConfig::create([
'field_name' => 'field_reference',
'entity_type' => 'media',
'type' => 'entity_reference',
'settings' => [
'target_type' => 'media',
],
])->save();
FieldConfig::create([
'field_name' => 'field_reference',
'entity_type' => 'media',
'bundle' => $media_type->id(),
])->save();
$author = $this->drupalCreateUser([
'view media',
'view own unpublished media',
]);
$other_user = $this->drupalCreateUser([
'view media',
'view own unpublished media',
]);
$view_user = $this->drupalCreateUser(['view media']);
$child_title = 'Child media';
$media_child = Media::create([
'name' => $child_title,
'bundle' => $media_type->id(),
'uid' => $author->id(),
]);
$media_child->setUnpublished()->save();
$media_parent = Media::create([
'name' => 'Parent media',
'bundle' => $media_type->id(),
'field_reference' => $media_child->id(),
]);
$media_parent->save();
\Drupal::service('entity_display.repository')->getViewDisplay('media', $media_type->id(), 'full')
->set('content', [])
->setComponent('title', ['type' => 'string'])
->setComponent('field_reference', [
'type' => 'entity_reference_label',
])
->save();
$assert_session = $this->assertSession();
// The author of the child media items should have access to both the parent
// and child.
$this->drupalLogin($author);
$this->drupalGet($media_parent->toUrl());
$this->assertCacheContext('user');
$assert_session->pageTextContains($child_title);
// Other users with the 'view own unpublished media' permission should not
// be able to see the unpublished child media item. The 'user' cache context
// should be added in this case.
$this->drupalLogin($other_user);
$this->drupalGet($media_parent->toUrl());
$this->assertCacheContext('user');
$assert_session->pageTextNotContains($child_title);
// User with just the 'view media' permission should not be able to see the
// child media item. The 'user' cache context should not be added in this
// case.
$this->drupalLogin($view_user);
$this->drupalGet($media_parent->toUrl());
$this->assertNoCacheContext('user');
$assert_session->pageTextNotContains($child_title);
}
}

View File

@ -0,0 +1,120 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
use Drupal\media\Entity\Media;
use Drupal\views\Views;
/**
* Tests a media bulk form.
*
* @group media
*/
class MediaBulkFormTest extends MediaFunctionalTestBase {
/**
* Modules to be enabled.
*
* @var array
*/
protected static $modules = ['media_test_views'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* The test media type.
*
* @var \Drupal\media\MediaTypeInterface
*/
protected $testMediaType;
/**
* The test media items.
*
* @var \Drupal\media\MediaInterface[]
*/
protected $mediaItems;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->testMediaType = $this->createMediaType('test');
// Create some test media items.
$this->mediaItems = [];
for ($i = 1; $i <= 5; $i++) {
$media = Media::create([
'bundle' => $this->testMediaType->id(),
]);
$media->save();
$this->mediaItems[] = $media;
}
}
/**
* Tests the media bulk form.
*/
public function testBulkForm(): void {
$session = $this->getSession();
$page = $session->getPage();
$assert_session = $this->assertSession();
// Check that all created items are present in the test view.
$view = Views::getView('test_media_bulk_form');
$view->execute();
$this->assertSame($view->total_rows, 5);
// Check the operations are accessible to the logged in user.
$this->drupalGet('test-media-bulk-form');
// Current available actions: Delete, Save, Publish, Unpublish.
$available_actions = [
'media_delete_action',
'media_publish_action',
'media_save_action',
'media_unpublish_action',
];
foreach ($available_actions as $action_name) {
$assert_session->optionExists('action', $action_name);
}
// Test unpublishing in bulk.
$page->checkField('media_bulk_form[0]');
$page->checkField('media_bulk_form[1]');
$page->checkField('media_bulk_form[2]');
$page->selectFieldOption('action', 'media_unpublish_action');
$page->pressButton('Apply to selected items');
$assert_session->pageTextContains('Unpublish media was applied to 3 items');
$this->assertFalse($this->storage->loadUnchanged(1)->isPublished(), 'The unpublish action failed in some of the media items.');
$this->assertFalse($this->storage->loadUnchanged(2)->isPublished(), 'The unpublish action failed in some of the media items.');
$this->assertFalse($this->storage->loadUnchanged(3)->isPublished(), 'The unpublish action failed in some of the media items.');
// Test publishing in bulk.
$page->checkField('media_bulk_form[0]');
$page->checkField('media_bulk_form[1]');
$page->selectFieldOption('action', 'media_publish_action');
$page->pressButton('Apply to selected items');
$assert_session->pageTextContains('Publish media was applied to 2 items');
$this->assertTrue($this->storage->loadUnchanged(1)->isPublished(), 'The publish action failed in some of the media items.');
$this->assertTrue($this->storage->loadUnchanged(2)->isPublished(), 'The publish action failed in some of the media items.');
// Test deletion in bulk.
$page->checkField('media_bulk_form[0]');
$page->checkField('media_bulk_form[1]');
$page->selectFieldOption('action', 'media_delete_action');
$page->pressButton('Apply to selected items');
$assert_session->pageTextContains('Are you sure you want to delete these media items?');
$page->pressButton('Delete');
$assert_session->pageTextContains('Deleted 2 items.');
$this->assertNull($this->storage->loadUnchanged(1), 'Could not delete some of the media items.');
$this->assertNull($this->storage->loadUnchanged(2), 'Could not delete some of the media items.');
}
}

View File

@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
use Drupal\Core\Entity\EntityInterface;
use Drupal\media\Entity\Media;
use Drupal\Tests\media\Traits\MediaTypeCreationTrait;
use Drupal\Tests\system\Functional\Entity\EntityWithUriCacheTagsTestBase;
/**
* Tests the media items cache tags.
*
* @group media
*/
class MediaCacheTagsTest extends EntityWithUriCacheTagsTestBase {
use MediaTypeCreationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'media',
'media_test_source',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
\Drupal::configFactory()
->getEditable('media.settings')
->set('standalone_url', TRUE)
->save(TRUE);
$this->container->get('router.builder')->rebuild();
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
// Create a media type.
$mediaType = $this->createMediaType('test');
// Create a media item.
$media = Media::create([
'bundle' => $mediaType->id(),
'name' => 'Unnamed',
]);
$media->save();
return $media;
}
/**
* {@inheritdoc}
*/
protected function getAdditionalCacheContextsForEntity(EntityInterface $media): array {
return ['timezone'];
}
/**
* {@inheritdoc}
*/
protected function getAdditionalCacheTagsForEntity(EntityInterface $media): array {
// Each media item must have an author and a thumbnail.
return [
'user:' . $media->getOwnerId(),
'config:image.style.thumbnail',
'file:' . $media->get('thumbnail')->entity->id(),
];
}
}

View File

@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
use Drupal\media\Entity\Media;
/**
* Tests views contextual links on media items.
*
* @group media
*/
class MediaContextualLinksTest extends MediaFunctionalTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'contextual',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests contextual links.
*/
public function testMediaContextualLinks(): void {
\Drupal::configFactory()
->getEditable('media.settings')
->set('standalone_url', TRUE)
->save(TRUE);
$this->container->get('router.builder')->rebuild();
// Create a media type.
$mediaType = $this->createMediaType('test');
// Create a media item.
$media = Media::create([
'bundle' => $mediaType->id(),
'name' => 'Unnamed',
]);
$media->save();
$user = $this->drupalCreateUser([
'administer media',
'access contextual links',
]);
$this->drupalLogin($user);
$this->drupalGet('media/' . $media->id());
$this->assertSession()->elementAttributeContains('css', 'div[data-contextual-id]', 'data-contextual-id', 'media:media=' . $media->id() . ':');
}
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
use Drupal\Tests\BrowserTestBase;
use Drupal\Tests\media\Traits\MediaTypeCreationTrait;
/**
* Base class for Media functional tests.
*/
abstract class MediaFunctionalTestBase extends BrowserTestBase {
use MediaFunctionalTestTrait;
use MediaTypeCreationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'system',
'node',
'field_ui',
'views_ui',
'media',
'media_test_source',
];
}

View File

@ -0,0 +1,73 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
/**
* Trait with helpers for Media functional tests.
*/
trait MediaFunctionalTestTrait {
/**
* Permissions for the admin user that will be logged-in for test.
*
* @var array
*/
protected static $adminUserPermissions = [
// Media module permissions.
'access media overview',
'administer media',
'administer media fields',
'administer media form display',
'administer media display',
'administer media types',
'view media',
// Other permissions.
'administer views',
'access content overview',
'view all revisions',
'administer content types',
'administer node fields',
'administer node form display',
'administer node display',
'bypass node access',
];
/**
* An admin test user account.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $adminUser;
/**
* A non-admin test user account.
*
* @var \Drupal\user\UserInterface
*/
protected $nonAdminUser;
/**
* The storage service.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $storage;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Have two users ready to be used in tests.
$this->adminUser = $this->drupalCreateUser(static::$adminUserPermissions);
$this->nonAdminUser = $this->drupalCreateUser([]);
// Start off logged in as admin.
$this->drupalLogin($this->adminUser);
$this->storage = $this->container->get('entity_type.manager')->getStorage('media');
}
}

View File

@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
use Drupal\Tests\BrowserTestBase;
/**
* Tests media Install / Uninstall logic.
*
* @group media
*/
class MediaInstallTest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['media'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->drupalLogin($this->drupalCreateUser(['administer modules']));
}
/**
* Tests reinstalling after being uninstalled.
*/
public function testReinstallAfterUninstall(): void {
$page = $this->getSession()->getPage();
$assert_session = $this->assertSession();
// Uninstall the media module.
$this->container->get('module_installer')->uninstall(['media'], FALSE);
$this->drupalGet('/admin/modules');
$page->checkField('modules[media][enable]');
$page->pressButton('Install');
$assert_session->pageTextNotContains('could not be moved/copied because a file by that name already exists in the destination directory');
$assert_session->pageTextContains('Module Media has been installed');
}
}

View File

@ -0,0 +1,297 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\media\Entity\Media;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\file\Entity\File;
use Drupal\Tests\TestFileCreationTrait;
/**
* Tests the Media overview page.
*
* @group media
*/
class MediaOverviewPageTest extends MediaFunctionalTestBase {
use TestFileCreationTrait;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $modules = ['language'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Make the site multilingual to have a working language field handler.
ConfigurableLanguage::create(['id' => 'es', 'title' => 'Spanish title', 'label' => 'Spanish label'])->save();
$this->drupalLogin($this->nonAdminUser);
}
/**
* Tests that the Media overview page (/admin/content/media).
*/
public function testMediaOverviewPage(): void {
$assert_session = $this->assertSession();
// Check the view exists, is access-restricted, and some defaults are there.
$this->drupalGet('/admin/content/media');
$assert_session->statusCodeEquals(403);
$role = Role::load(RoleInterface::AUTHENTICATED_ID);
$this->grantPermissions($role, ['access media overview']);
$this->getSession()->reload();
$assert_session->statusCodeEquals(200);
$assert_session->titleEquals('Media | Drupal');
$assert_session->fieldExists('Media name');
$assert_session->selectExists('type');
$assert_session->selectExists('status');
$assert_session->selectExists('langcode');
$assert_session->buttonExists('Filter');
$header = $assert_session->elementExists('css', 'th#view-thumbnail-target-id-table-column');
$this->assertSame('Thumbnail', $header->getText());
$header = $assert_session->elementExists('css', 'th#view-name-table-column');
$this->assertSame('Media name', $header->getText());
$header = $assert_session->elementExists('css', 'th#view-bundle-table-column');
$this->assertSame('Type', $header->getText());
$header = $assert_session->elementExists('css', 'th#view-uid-table-column');
$this->assertSame('Author', $header->getText());
$header = $assert_session->elementExists('css', 'th#view-status-table-column');
$this->assertSame('Status', $header->getText());
$header = $assert_session->elementExists('css', 'th#view-changed-table-column');
$this->assertSame('Updated Sort ascending', $header->getText());
$header = $assert_session->elementExists('css', 'th#view-operations-table-column');
$this->assertSame('Operations', $header->getText());
$assert_session->pageTextContains('No media available.');
// Create some content for the view.
$media_type1 = $this->createMediaType('test');
$media_type2 = $this->createMediaType('test');
$media1 = Media::create([
'bundle' => $media_type1->id(),
'name' => 'Media 1',
'uid' => $this->adminUser->id(),
]);
$media1->save();
$media2 = Media::create([
'bundle' => $media_type2->id(),
'name' => 'Media 2',
'uid' => $this->adminUser->id(),
'status' => FALSE,
'changed' => time() - 50,
]);
$media2->save();
$media3 = Media::create([
'bundle' => $media_type1->id(),
'name' => 'Media 3',
'uid' => $this->nonAdminUser->id(),
'changed' => time() - 100,
]);
$media3->save();
// Make sure the role save below properly invalidates cache tags.
$this->refreshVariables();
// Verify the view is now correctly populated. The non-admin user can only
// view published media.
$this->grantPermissions($role, [
'view media',
'update any media',
'delete any media',
]);
$this->getSession()->reload();
$row1 = $assert_session->elementExists('css', 'table tbody tr:nth-child(1)');
$row2 = $assert_session->elementExists('css', 'table tbody tr:nth-child(2)');
// Media thumbnails.
$assert_session->elementExists('css', 'td.views-field-thumbnail__target-id img', $row1);
$assert_session->elementExists('css', 'td.views-field-thumbnail__target-id img', $row2);
// Media names.
$name1 = $assert_session->elementExists('css', 'td.views-field-name a', $row1);
$this->assertSame($media1->label(), $name1->getText());
$name2 = $assert_session->elementExists('css', 'td.views-field-name a', $row2);
$this->assertSame($media3->label(), $name2->getText());
$assert_session->linkByHrefExists('/media/' . $media1->id());
$assert_session->linkByHrefExists('/media/' . $media3->id());
// Media types.
$type_element1 = $assert_session->elementExists('css', 'td.views-field-bundle', $row1);
$this->assertSame($media_type1->label(), $type_element1->getText());
$type_element2 = $assert_session->elementExists('css', 'td.views-field-bundle', $row2);
$this->assertSame($media_type1->label(), $type_element2->getText());
// Media authors.
$author_element1 = $assert_session->elementExists('css', 'td.views-field-uid', $row1);
$this->assertSame($this->adminUser->getDisplayName(), $author_element1->getText());
$author_element3 = $assert_session->elementExists('css', 'td.views-field-uid', $row2);
$this->assertSame($this->nonAdminUser->getDisplayName(), $author_element3->getText());
// Media publishing status.
$status_element1 = $assert_session->elementExists('css', 'td.views-field-status', $row1);
$this->assertSame('Published', $status_element1->getText());
$status_element3 = $assert_session->elementExists('css', 'td.views-field-status', $row2);
$this->assertSame('Published', $status_element3->getText());
// Timestamp.
$expected = \Drupal::service('date.formatter')->format($media1->getChangedTime(), 'short');
$changed_element1 = $assert_session->elementExists('css', 'td.views-field-changed', $row1);
$this->assertSame($expected, $changed_element1->getText());
// Operations.
$assert_session->elementExists('css', 'td.views-field-operations li a:contains("Edit")', $row1);
$assert_session->linkByHrefExists('/media/' . $media1->id() . '/edit');
$assert_session->elementExists('css', 'td.views-field-operations li a:contains("Delete")', $row1);
$assert_session->linkByHrefExists('/media/' . $media1->id() . '/delete');
// Make sure the role save below properly invalidates cache tags.
$this->refreshVariables();
// Make the user the owner of the unpublished media item and assert the
// media item is only visible with the 'view own unpublished media'
// permission.
$media2->setOwner($this->nonAdminUser)->save();
$this->getSession()->reload();
$assert_session->pageTextNotContains($media2->label());
$role->grantPermission('view own unpublished media')->save();
$this->getSession()->reload();
$row = $assert_session->elementExists('css', 'table tbody tr:nth-child(2)');
$name = $assert_session->elementExists('css', 'td.views-field-name a', $row);
$this->assertSame($media2->label(), $name->getText());
$status_element = $assert_session->elementExists('css', 'td.views-field-status', $row);
$this->assertSame('Unpublished', $status_element->getText());
// Assert the admin user can always view all media.
$this->drupalLogin($this->adminUser);
$this->drupalGet('/admin/content/media');
$row1 = $assert_session->elementExists('css', 'table tbody tr:nth-child(1)');
$row2 = $assert_session->elementExists('css', 'table tbody tr:nth-child(2)');
$row3 = $assert_session->elementExists('css', 'table tbody tr:nth-child(3)');
$name1 = $assert_session->elementExists('css', 'td.views-field-name a', $row1);
$this->assertSame($media1->label(), $name1->getText());
$name2 = $assert_session->elementExists('css', 'td.views-field-name a', $row2);
$this->assertSame($media2->label(), $name2->getText());
$name3 = $assert_session->elementExists('css', 'td.views-field-name a', $row3);
$this->assertSame($media3->label(), $name3->getText());
$assert_session->linkByHrefExists('/media/' . $media1->id());
$assert_session->linkByHrefExists('/media/' . $media2->id());
$assert_session->linkByHrefExists('/media/' . $media3->id());
}
/**
* Tests the display of the alt attribute.
*/
public function testImageAltTextDisplay(): void {
$this->drupalLogin($this->adminUser);
$media_type = $this->createMediaType('image');
$media_type_id = $media_type->id();
$media_type->setFieldMap(['name' => 'name']);
$media_type->save();
/** @var \Drupal\field\FieldConfigInterface $field */
$field = FieldConfig::load("media.$media_type_id.field_media_image");
$settings = $field->getSettings();
$settings['alt_field'] = TRUE;
$settings['alt_field_required'] = FALSE;
$field->set('settings', $settings);
$field->save();
$file = File::create([
'uri' => $this->getTestFiles('image')[0]->uri,
]);
$file->save();
// Set the alt text to an empty string.
$media = Media::create([
'name' => 'Custom name',
'bundle' => $media_type_id,
'field_media_image' => [
[
'target_id' => $file->id(),
'alt' => '',
'title' => 'default title',
],
],
]);
$media->save();
$this->drupalGet('/admin/content/media');
// Confirm that the alt text attribute is present.
$assert_session = $this->assertSession();
$element = $assert_session->elementAttributeExists('css', 'td.views-field-thumbnail__target-id img', 'alt');
$this->assertSame('', (string) $element->getAttribute('alt'));
}
/**
* Tests the author views filter uses the user_name plugin.
*/
public function testMediaOverviewAuthorFilter(): void {
$this->drupalLogin($this->adminUser);
// Create some content for the view.
$media_type1 = $this->createMediaType('test');
$media1 = Media::create([
'bundle' => $media_type1->id(),
'name' => 'Media 1',
'uid' => $this->adminUser->id(),
]);
$media1->save();
$media2 = Media::create([
'bundle' => $media_type1->id(),
'name' => 'Media 2',
'uid' => $this->adminUser->id(),
]);
$media2->save();
$media3 = Media::create([
'bundle' => $media_type1->id(),
'name' => 'Media 3',
'uid' => $this->nonAdminUser->id(),
]);
$media3->save();
// Add the media author filter to the media overview view.
$this->drupalGet('admin/structure/views/nojs/add-handler/media/media_page_list/filter');
$edit = [
'name[media_field_data.user_name]' => 1,
];
$this->submitForm($edit, 'Add and configure filter criteria');
$edit = [
'options[expose_button][checkbox][checkbox]' => 1,
];
$this->submitForm($edit, 'Expose filter');
$edit = [
'options[expose_button][checkbox][checkbox]' => 1,
'options[group_button][radios][radios]' => 0,
];
$this->submitForm($edit, 'Apply');
$this->submitForm([], 'Save');
$role = Role::load(RoleInterface::AUTHENTICATED_ID);
$this->grantPermissions($role, ['access media overview']);
$this->drupalGet('/admin/content/media');
$this->assertSession()->statusCodeEquals(200);
// Assert we are using the user_name filter.
$this->assertSession()->pageTextContains('Authored by');
$this->submitForm([
'user_name' => $this->adminUser->getAccountName() . ' (' . $this->adminUser->id() . ')',
], 'Filter');
$this->assertSession()->linkByHrefExists('/media/' . $media1->id());
$this->assertSession()->linkByHrefExists('/media/' . $media2->id());
$this->assertSession()->linkByHrefNotExists('/media/' . $media3->id());
}
}

View File

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
/**
* Tests the Media module's requirements checks.
*
* @group media
*/
class MediaRequirementsTest extends MediaFunctionalTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests that the requirements check can handle a missing source field.
*/
public function testMissingSourceFieldDefinition(): void {
$media_type = $this->createMediaType('test');
/** @var \Drupal\field\FieldConfigInterface $field_definition */
$field_definition = $media_type->getSource()
->getSourceFieldDefinition($media_type);
/** @var \Drupal\field\FieldStorageConfigInterface $field_storage_definition */
$field_storage_definition = $field_definition->getFieldStorageDefinition();
$field_definition->delete();
$field_storage_definition->delete();
$valid_media_type = $this->createMediaType('test');
$permissions = [
'administer site configuration',
];
$this->drupalLogin($this->drupalCreateUser($permissions));
$this->drupalGet('/admin/reports/status');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->pageTextContains("The source field definition for the {$media_type->label()} media type is missing.");
$this->assertSession()->pageTextNotContains("The source field definition for the {$valid_media_type->label()} media type is missing.");
}
}

View File

@ -0,0 +1,350 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
use Drupal\Core\Entity\EntityInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\media\Entity\Media;
use Drupal\media\MediaInterface;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
/**
* Tests the revisions of media entities.
*
* @group media
*/
class MediaRevisionTest extends MediaFunctionalTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->createMediaType('test', ['id' => 'test', 'label' => 'test']);
}
/**
* Creates a media item.
*
* @param string $title
* Title of media item.
*
* @return \Drupal\media\Entity\Media
* A media item.
*/
protected function createMedia(string $title): Media {
$media = Media::create([
'bundle' => 'test',
'name' => $title,
]);
$media->save();
return $media;
}
/**
* Checks media revision operations.
*/
public function testRevisions(): void {
$assert = $this->assertSession();
$media = $this->createMedia('Sample media');
$originalRevisionId = $media->getRevisionId();
// You can access the revision page when there is only 1 revision.
$this->drupalGet($media->toUrl('revision'));
$assert->statusCodeEquals(200);
// Create some revisions.
$revision_count = 3;
for ($i = 0; $i < $revision_count; $i++) {
$media->revision_log = $this->randomMachineName(32);
$media = $this->createMediaRevision($media);
}
// Confirm that the last revision is the default revision.
$this->assertTrue($media->isDefaultRevision(), 'Last revision is the default.');
// Get the original revision for simple checks.
$media = \Drupal::entityTypeManager()->getStorage('media')
->loadRevision($originalRevisionId);
// Test permissions.
$this->drupalLogin($this->nonAdminUser);
/** @var \Drupal\user\RoleInterface $role */
$role = Role::load(RoleInterface::AUTHENTICATED_ID);
// Test 'view all media revisions' permission ('view media' permission is
// needed as well).
user_role_revoke_permissions($role->id(), [
'view all media revisions',
]);
$this->drupalGet($media->toUrl('revision'));
$assert->statusCodeEquals(403);
$this->grantPermissions($role, ['view any test media revisions']);
$this->drupalGet($media->toUrl('revision'));
$assert->statusCodeEquals(200);
user_role_revoke_permissions($role->id(), ['view any test media revisions']);
$this->grantPermissions($role, ['view all media revisions']);
$this->drupalGet($media->toUrl('revision'));
$assert->statusCodeEquals(200);
// Confirm the revision page shows the correct title.
$assert->pageTextContains($media->getName());
}
/**
* Tests creating revisions of a File media item.
*/
public function testFileMediaRevision(): void {
$assert = $this->assertSession();
$uri = 'temporary://foo.txt';
file_put_contents($uri, $this->randomString(128));
$this->createMediaType('file', ['id' => 'document', 'new_revision' => TRUE]);
// Create a media item.
$this->drupalGet('/media/add/document');
$page = $this->getSession()->getPage();
$page->fillField('Name', 'Foobar');
$page->attachFileToField('File', $this->container->get('file_system')->realpath($uri));
$page->pressButton('Save');
$assert->addressEquals('admin/content/media');
// The media item was just created, so it should only have one revision.
$media = $this->container
->get('entity_type.manager')
->getStorage('media')
->load(1);
$this->assertRevisionCount($media, 1);
// If we edit the item, we should get a new revision.
$this->drupalGet('/media/1/edit');
$assert->checkboxChecked('Create new revision');
$page = $this->getSession()->getPage();
$page->fillField('Name', 'Foo');
$page->pressButton('Save');
$this->assertRevisionCount($media, 2);
// Confirm the correct revision title appears on "view revisions" page.
$media = $this->container->get('entity_type.manager')
->getStorage('media')
->loadUnchanged(1);
$this->drupalGet("media/" . $media->id() . "/revisions/" . $media->getRevisionId() . "/view");
$assert->pageTextContains('Foo');
}
/**
* Tests creating revisions of an Image media item.
*/
public function testImageMediaRevision(): void {
$assert = $this->assertSession();
$this->createMediaType('image', ['id' => 'image', 'new_revision' => TRUE]);
/** @var \Drupal\field\FieldConfigInterface $field */
// Disable the alt text field, because this is not a JavaScript test and
// the alt text field will therefore not appear without a full page refresh.
$field = FieldConfig::load('media.image.field_media_image');
$settings = $field->getSettings();
$settings['alt_field'] = FALSE;
$settings['alt_field_required'] = FALSE;
$field->set('settings', $settings);
$field->save();
// Create a media item.
$this->drupalGet('/media/add/image');
$page = $this->getSession()->getPage();
$page->fillField('Name', 'Foobar');
$page->attachFileToField('Image', $this->root . '/core/modules/media/tests/fixtures/example_1.jpeg');
$page->pressButton('Save');
$assert->addressEquals('admin/content/media');
// The media item was just created, so it should only have one revision.
$media = $this->container
->get('entity_type.manager')
->getStorage('media')
->load(1);
$this->assertRevisionCount($media, 1);
// If we edit the item, we should get a new revision.
$this->drupalGet('/media/1/edit');
$assert->checkboxChecked('Create new revision');
$page = $this->getSession()->getPage();
$page->fillField('Name', 'Foo');
$page->pressButton('Save');
$this->assertRevisionCount($media, 2);
// Confirm the correct revision title appears on "view revisions" page.
$media = $this->container->get('entity_type.manager')
->getStorage('media')
->loadUnchanged(1);
$this->drupalGet("media/" . $media->id() . "/revisions/" . $media->getRevisionId() . "/view");
$assert->pageTextContains('Foo');
}
/**
* Creates a new revision for a given media item.
*
* @param \Drupal\media\MediaInterface $media
* A media object.
*
* @return \Drupal\media\MediaInterface
* A media object with up to date revision information.
*/
protected function createMediaRevision(MediaInterface $media) {
$media->setName($this->randomMachineName());
$media->setNewRevision();
$media->save();
return $media;
}
/**
* Asserts that an entity has a certain number of revisions.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity in question.
* @param int $expected_revisions
* The expected number of revisions.
*
* @internal
*/
protected function assertRevisionCount(EntityInterface $entity, int $expected_revisions): void {
$entity_type = $entity->getEntityType();
$count = $this->container
->get('entity_type.manager')
->getStorage($entity_type->id())
->getQuery()
->accessCheck(FALSE)
->count()
->allRevisions()
->condition($entity_type->getKey('id'), $entity->id())
->execute();
$this->assertSame($expected_revisions, (int) $count);
}
/**
* Creates a media with a revision.
*
* @param \Drupal\media\Entity\Media $media
* The media object.
*/
private function createMediaWithRevision(Media $media): void {
$media->setNewRevision();
$media->setName('1st changed title');
$media->setRevisionLogMessage('first revision');
// Set revision creation time to check the confirmation message while
// deleting or reverting a revision.
$media->setRevisionCreationTime((new \DateTimeImmutable('11 January 2009 4pm'))->getTimestamp());
$media->save();
}
/**
* Tests deleting a revision.
*/
public function testRevisionDelete(): void {
$user = $this->drupalCreateUser([
'edit any test media',
'view any test media revisions',
'delete any test media revisions',
]);
$this->drupalLogin($user);
$media = $this->createMedia('Sample media');
$this->createMediaWithRevision($media);
$originalRevisionId = $media->getRevisionId();
// Cannot delete latest revision.
$this->drupalGet($media->toUrl('revision-delete-form'));
$this->assertSession()->statusCodeEquals(403);
// Create a new revision.
$media->setNewRevision();
$media->setRevisionLogMessage('second revision')
->setRevisionCreationTime((new \DateTimeImmutable('12 March 2012 5pm'))->getTimestamp())
->setName('Sample media updated')
->save();
$this->drupalGet($media->toUrl('version-history'));
$this->assertSession()->pageTextContains("First revision");
$this->assertSession()->pageTextContains("Second revision");
$this->assertSession()->elementsCount('css', 'table tbody tr', 3);
// Reload the previous revision, and ensure we can delete it in the UI.
$revision = \Drupal::entityTypeManager()->getStorage('media')
->loadRevision($originalRevisionId);
$this->drupalGet($revision->toUrl('revision-delete-form'));
$this->assertSession()->pageTextContains('Are you sure you want to delete the revision from Sun, 11 Jan 2009 - 16:00?');
$this->submitForm([], 'Delete');
$this->assertSession()->pageTextNotContains("First revision");
$this->assertSession()->pageTextContains("Second revision");
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->addressEquals(sprintf('media/%s/revisions', $media->id()));
$this->assertSession()->pageTextContains('Revision from Sun, 11 Jan 2009 - 16:00 of test 1st changed title has been deleted.');
// Check that only two revisions exists, i.e. the original and the latest
// revision.
$this->assertSession()->elementsCount('css', 'table tbody tr', 2);
}
/**
* Tests reverting a revision.
*/
public function testRevisionRevert(): void {
/** @var \Drupal\user\UserInterface $user */
$user = $this->drupalCreateUser([
'edit any test media',
'view any test media revisions',
'revert any test media revisions',
]);
$this->drupalLogin($user);
$media = $this->createMedia('Initial title');
$this->createMediaWithRevision($media);
$originalRevisionId = $media->getRevisionId();
$originalRevisionLabel = $media->getName();
// Cannot revert latest revision.
$this->drupalGet($media->toUrl('revision-revert-form'));
$this->assertSession()->statusCodeEquals(403);
// Create a new revision.
$media->setNewRevision();
$media->setRevisionLogMessage('Second revision')
->setRevisionCreationTime((new \DateTimeImmutable('12 March 2012 5pm'))->getTimestamp())
->setName('Sample media updated')
->save();
$this->drupalGet($media->toUrl('version-history'));
$this->assertSession()->pageTextContains("First revision");
$this->assertSession()->pageTextContains("Second revision");
$this->assertSession()->elementsCount('css', 'table tbody tr', 3);
// Reload the previous revision, and ensure we can revert to it in the UI.
$revision = \Drupal::entityTypeManager()->getStorage('media')
->loadRevision($originalRevisionId);
$this->drupalGet($revision->toUrl('revision-revert-form'));
$this->assertSession()->pageTextContains('Are you sure you want to revert to the revision from Sun, 11 Jan 2009 - 16:00?');
$this->submitForm([], 'Revert');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->pageTextContains('Copy of the revision from Sun, 11 Jan 2009 - 16:00');
$this->assertSession()->addressEquals(sprintf('media/%s/revisions', $media->id()));
$this->assertSession()->pageTextContains(sprintf('test %s has been reverted to the revision from Sun, 11 Jan 2009 - 16:00.', $originalRevisionLabel));
$this->assertSession()->elementsCount('css', 'table tbody tr', 4);
$this->drupalGet($media->toUrl('edit-form'));
// Check if the title is changed to the reverted revision.
$this->assertSession()->pageTextContains('1st changed title');
}
}

View File

@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
use Drupal\Core\Url;
/**
* Testing the media settings.
*
* @group media
*/
class MediaSettingsTest extends MediaFunctionalTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->drupalLogin($this->createUser([
'administer site configuration',
'administer media',
]));
}
/**
* Tests that the media settings form stores a `null` iFrame domain.
*/
public function testSettingsForm(): void {
$assert_session = $this->assertSession();
$this->assertNull($this->config('media.settings')->get('iframe_domain'));
$this->drupalGet(Url::fromRoute('media.settings'));
$assert_session->fieldExists('iframe_domain');
// Explicitly submitting an empty string does not result in the
// `iframe_domain` property getting set to the empty string: it is converted
// to `null` to comply with the config schema.
// @see \Drupal\media\Form\MediaSettingsForm::submitForm()
$this->submitForm([
'iframe_domain' => '',
], 'Save configuration');
$assert_session->statusMessageContains('The configuration options have been saved.', 'status');
$this->assertNull($this->config('media.settings')->get('iframe_domain'));
}
}

View File

@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
use Drupal\field\Entity\FieldConfig;
/**
* Tests the file media source.
*
* @group media
*/
class MediaSourceFileTest extends MediaFunctionalTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests that it's possible to change the allowed file extensions.
*/
public function testSourceFieldSettingsEditing(): void {
$session = $this->getSession();
$page = $session->getPage();
$assert_session = $this->assertSession();
$media_type = $this->createMediaType('file');
$media_type_id = $media_type->id();
$this->assertSame('txt doc docx pdf', FieldConfig::load("media.$media_type_id.field_media_file")->get('settings')['file_extensions']);
$this->drupalGet("admin/structure/media/manage/$media_type_id/fields/media.$media_type_id.field_media_file");
// File extension field exists.
$assert_session->fieldExists('Allowed file extensions');
// Add another extension.
$page->fillField('settings[file_extensions]', 'txt, doc, docx, pdf, odt');
$page->pressButton('Save settings');
$this->drupalGet("admin/structure/media/manage/$media_type_id/fields/media.$media_type_id.field_media_file");
// Verify that new extension is present.
$assert_session->fieldValueEquals('settings[file_extensions]', 'txt, doc, docx, pdf, odt');
$this->assertSame('txt doc docx pdf odt', FieldConfig::load("media.$media_type_id.field_media_file")->get('settings')['file_extensions']);
}
/**
* Ensure source field deletion is not possible.
*/
public function testPreventSourceFieldDeletion(): void {
$media_type = $this->createMediaType('file');
$media_type_id = $media_type->id();
$this->drupalGet("admin/structure/media/manage/$media_type_id/fields/media.$media_type_id.field_media_file/delete");
$this->assertSession()->statusCodeEquals(403);
}
}

View File

@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
use Drupal\field\Entity\FieldConfig;
use Drupal\file\Entity\File;
use Drupal\media\Entity\Media;
use Drupal\Tests\TestFileCreationTrait;
/**
* Tests the image media source.
*
* @group media
*/
class MediaSourceImageTest extends MediaFunctionalTestBase {
use TestFileCreationTrait;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Test that non-main properties do not trigger source field value change.
*/
public function testOnlyMainPropertiesTriggerSourceFieldChanged(): void {
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
$media_type = $this->createMediaType('image');
$media_type_id = $media_type->id();
$media_type->setFieldMap(['name' => 'name']);
$media_type->save();
/** @var \Drupal\field\FieldConfigInterface $field */
// Disable the alt text field, because this is not a JavaScript test and
// the alt text field will therefore not appear without a full page refresh.
$field = FieldConfig::load("media.$media_type_id.field_media_image");
$settings = $field->getSettings();
$settings['alt_field'] = TRUE;
$settings['alt_field_required'] = FALSE;
$field->set('settings', $settings);
$field->save();
$file = File::create([
'uri' => $this->getTestFiles('image')[0]->uri,
]);
$file->save();
$media = Media::create([
'name' => 'Custom name',
'bundle' => $media_type_id,
'field_media_image' => $file->id(),
]);
$media->save();
// Change only the alt of the image.
$this->drupalGet($media->toUrl('edit-form'));
$this->submitForm(['field_media_image[0][alt]' => 'Alt text'], 'Save');
// Custom name should stay.
$this->drupalGet($media->toUrl('edit-form'));
$assert_session->fieldValueEquals('name[0][value]', 'Custom name');
// Remove image and attach a new one.
$this->submitForm([], 'Remove');
$image_media_name = 'example_1.jpeg';
$page->attachFileToField('files[field_media_image_0]', $this->root . '/core/modules/media/tests/fixtures/' . $image_media_name);
$page->pressButton('Save');
$this->drupalGet($media->toUrl('edit-form'));
$assert_session->fieldValueEquals('name[0][value]', 'example_1.jpeg');
}
}

View File

@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
use Drupal\media\Entity\Media;
/**
* Tests media template suggestions.
*
* @group media
*/
class MediaTemplateSuggestionsTest extends MediaFunctionalTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['media'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests template suggestions from media_theme_suggestions_media().
*/
public function testMediaThemeHookSuggestions(): void {
$media_type = $this->createMediaType('test', [
'queue_thumbnail_downloads' => FALSE,
]);
// Create media item to be rendered.
$media = Media::create([
'bundle' => $media_type->id(),
'name' => 'Unnamed',
]);
$media->save();
$view_mode = 'full';
// Simulate theming of the media item.
$build = \Drupal::entityTypeManager()->getViewBuilder('media')->view($media, $view_mode);
$variables['elements'] = $build;
$suggestions = \Drupal::moduleHandler()->invokeAll('theme_suggestions_media', [$variables]);
$this->assertSame($suggestions, ['media__full', 'media__' . $media_type->id(), 'media__' . $media_type->id() . '__full', 'media__source_' . $media_type->getSource()->getPluginId()], 'Found expected media suggestions.');
}
}

View File

@ -0,0 +1,106 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
use Drupal\Tests\content_translation\Functional\ContentTranslationUITestBase;
use Drupal\Tests\media\Traits\MediaTypeCreationTrait;
/**
* Tests the Media Translation UI.
*
* @group media
*/
class MediaTranslationUITest extends ContentTranslationUITestBase {
use MediaTypeCreationTrait;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected $defaultCacheContexts = [
'languages:language_interface',
'session',
'theme',
'url.path',
'url.query_args',
'user.permissions',
'user.roles:authenticated',
];
/**
* {@inheritdoc}
*/
protected static $modules = [
'language',
'content_translation',
'media',
'media_test_source',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
$this->entityTypeId = 'media';
$this->bundle = 'test';
parent::setUp();
$this->doSetup();
}
/**
* {@inheritdoc}
*/
public function setupBundle(): void {
$this->createMediaType('test', [
'id' => $this->bundle,
'queue_thumbnail_downloads' => FALSE,
]);
}
/**
* {@inheritdoc}
*/
protected function getTranslatorPermissions(): array {
return array_merge(parent::getTranslatorPermissions(), [
'administer media',
'edit any test media',
]);
}
/**
* {@inheritdoc}
*/
protected function getEditorPermissions(): array {
return ['administer media', 'create test media'];
}
/**
* {@inheritdoc}
*/
protected function getAdministratorPermissions(): array {
return array_merge(parent::getAdministratorPermissions(), [
'access administration pages',
'administer media types',
'access media overview',
'administer languages',
]);
}
/**
* {@inheritdoc}
*/
protected function getNewEntityValues($langcode) {
return [
'name' => [['value' => $this->randomMachineName()]],
'field_media_test' => [['value' => $this->randomMachineName()]],
] + parent::getNewEntityValues($langcode);
}
}

View File

@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
use Drupal\media\Entity\MediaType;
use Drupal\TestTools\Random;
/**
* Ensures that media UI works correctly without JavaScript.
*
* @group media
*/
class MediaTypeCreationTest extends MediaFunctionalTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'media_test_source',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests the media type creation form with only the mandatory options.
*
* @dataProvider providerMediaTypeCreationForm
*/
public function testMediaTypeCreationForm($button_label, $address, $machine_name): void {
$this->drupalGet('/admin/structure/media/add');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->fieldExists('label')->setValue($this->randomString());
$this->assertSession()->fieldExists('id')->setValue($machine_name);
$this->assertSession()->selectExists('source')->selectOption('test');
$this->assertSession()->buttonExists($button_label)->press();
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->fieldValueEquals('Test config value', 'This is default value.');
$this->assertSession()->buttonExists($button_label)->press();
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->addressEquals($address);
$this->assertInstanceOf(MediaType::class, MediaType::load($machine_name));
}
/**
* Data provider for testMediaTypeCreationForm().
*/
public static function providerMediaTypeCreationForm() {
$machine_name = Random::machineName();
return [
[
'Save',
'admin/structure/media',
$machine_name,
],
[
'Save and manage fields',
'admin/structure/media/manage/' . $machine_name . '/fields',
$machine_name,
],
];
}
}

View File

@ -0,0 +1,273 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
use Drupal\Tests\field_ui\Traits\FieldUiTestTrait;
/**
* Ensures that media UI works correctly.
*
* @group media
*/
class MediaUiFunctionalTest extends MediaFunctionalTestBase {
use FieldUiTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'block',
'media_test_source',
'media',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->drupalPlaceBlock('local_actions_block');
$this->drupalPlaceBlock('local_tasks_block');
}
/**
* Tests the media actions (add/edit/delete).
*/
public function testMediaWithOnlyOneMediaType(): void {
$session = $this->getSession();
$page = $session->getPage();
$assert_session = $this->assertSession();
$media_type = $this->createMediaType('test', [
'queue_thumbnail_downloads' => FALSE,
]);
$this->drupalGet('media/add');
$assert_session->statusCodeEquals(200);
$assert_session->addressEquals('media/add/' . $media_type->id());
$assert_session->elementNotExists('css', '#edit-revision');
// Tests media add form.
$media_name = $this->randomMachineName();
$page->fillField('name[0][value]', $media_name);
$revision_log_message = $this->randomString();
$page->fillField('revision_log_message[0][value]', $revision_log_message);
$source_field = $this->randomString();
$page->fillField('field_media_test[0][value]', $source_field);
$page->pressButton('Save');
$media_id = $this->container->get('entity_type.manager')
->getStorage('media')
->getQuery()
->accessCheck(FALSE)
->execute();
$media_id = reset($media_id);
/** @var \Drupal\media\MediaInterface $media */
$media = $this->container->get('entity_type.manager')
->getStorage('media')
->loadUnchanged($media_id);
$this->assertSame($media->getRevisionLogMessage(), $revision_log_message);
$this->assertSame($media->getName(), $media_name);
// Tests media edit form.
$media_type->setNewRevision(FALSE);
$media_type->save();
$media_name2 = $this->randomMachineName();
$this->drupalGet('media/' . $media_id . '/edit');
$assert_session->checkboxNotChecked('edit-revision');
$media_name = $this->randomMachineName();
$page->fillField('name[0][value]', $media_name2);
$page->pressButton('Save');
/** @var \Drupal\media\MediaInterface $media */
$media = $this->container->get('entity_type.manager')
->getStorage('media')
->loadUnchanged($media_id);
$this->assertSame($media->getName(), $media_name2);
// Change the authored by field to an empty string, which should assign
// authorship to the anonymous user (uid 0).
$this->drupalGet('media/' . $media_id . '/edit');
$edit['uid[0][target_id]'] = '';
$this->submitForm($edit, 'Save');
/** @var \Drupal\media\MediaInterface $media */
$media = $this->container->get('entity_type.manager')
->getStorage('media')
->loadUnchanged($media_id);
$uid = $media->getOwnerId();
// Most SQL database drivers stringify fetches but entities are not
// necessarily stored in a SQL database. At the same time, NULL/FALSE/""
// won't do.
$this->assertTrue($uid === 0 || $uid === '0', 'Media authored by anonymous user.');
// Test that there is no empty vertical tabs element, if the container is
// empty (see #2750697).
// Make the "Publisher ID" and "Created" fields hidden.
$this->drupalGet('/admin/structure/media/manage/' . $media_type->id() . '/form-display');
$page->selectFieldOption('fields[created][parent]', 'hidden');
$page->selectFieldOption('fields[uid][parent]', 'hidden');
$page->pressButton('Save');
// Assure we are testing with a user without permission to manage revisions.
$this->drupalLogin($this->nonAdminUser);
// Check the container is not present.
$this->drupalGet('media/' . $media_id . '/edit');
$assert_session->elementNotExists('css', 'input.vertical-tabs__active-tab');
// Continue testing as admin.
$this->drupalLogin($this->adminUser);
// Enable revisions by default.
$previous_revision_id = $media->getRevisionId();
$media_type->setNewRevision(TRUE);
$media_type->save();
$this->drupalGet('media/' . $media_id . '/edit');
$assert_session->checkboxChecked('edit-revision');
$page->fillField('name[0][value]', $media_name);
$page->fillField('revision_log_message[0][value]', $revision_log_message);
$page->pressButton('Save');
$this->drupalGet('media/' . $media_id);
$assert_session->statusCodeEquals(404);
/** @var \Drupal\media\MediaInterface $media */
$media = $this->container->get('entity_type.manager')
->getStorage('media')
->loadUnchanged($media_id);
$this->assertSame($media->getRevisionLogMessage(), $revision_log_message);
$this->assertNotEquals($previous_revision_id, $media->getRevisionId());
// Test the status checkbox.
$this->drupalGet('media/' . $media_id . '/edit');
$page->uncheckField('status[value]');
$page->pressButton('Save');
/** @var \Drupal\media\MediaInterface $media */
$media = $this->container->get('entity_type.manager')
->getStorage('media')
->loadUnchanged($media_id);
$this->assertFalse($media->isPublished());
// Tests media delete form.
$this->drupalGet('media/' . $media_id . '/edit');
$page->clickLink('Delete');
$assert_session->pageTextContains('This action cannot be undone');
$page->pressButton('Delete');
$media_id = \Drupal::entityQuery('media')->accessCheck(FALSE)->execute();
$this->assertEmpty($media_id);
}
/**
* Tests the "media/add" page.
*
* Tests if the "media/add" page gives you a selecting option if there are
* multiple media types available.
*/
public function testMediaWithMultipleMediaTypes(): void {
$assert_session = $this->assertSession();
// Tests and creates the first media type.
$first_media_type = $this->createMediaType('test', ['description' => $this->randomMachineName()]);
// Test and create a second media type.
$second_media_type = $this->createMediaType('test', ['description' => $this->randomMachineName()]);
// Test if media/add displays two media type options.
$this->drupalGet('media/add');
// Checks for the first media type.
$assert_session->pageTextContains($first_media_type->label());
$assert_session->pageTextContains($first_media_type->getDescription());
// Checks for the second media type.
$assert_session->pageTextContains($second_media_type->label());
$assert_session->pageTextContains($second_media_type->getDescription());
}
/**
* Tests that media in ER fields use the Rendered Entity formatter by default.
*/
public function testRenderedEntityReferencedMedia(): void {
$assert_session = $this->assertSession();
$this->drupalCreateContentType(['type' => 'page', 'name' => 'Page']);
$this->createMediaType('image', ['id' => 'image', 'new_revision' => TRUE]);
$this->fieldUIAddNewField('/admin/structure/types/manage/page', 'foo_field', 'Foo field', 'field_ui:entity_reference:media', [], ['settings[handler_settings][target_bundles][image]' => TRUE]);
$this->drupalGet('/admin/structure/types/manage/page/display');
$assert_session->fieldValueEquals('fields[field_foo_field][type]', 'entity_reference_entity_view');
}
/**
* Tests the redirect URL after creating a media item.
*/
public function testMediaCreateRedirect(): void {
$session = $this->getSession();
$page = $session->getPage();
$assert_session = $this->assertSession();
$this->createMediaType('test', [
'queue_thumbnail_downloads' => FALSE,
]);
// Test a redirect to the media canonical URL for a user without the 'access
// media overview' permission.
$this->drupalLogin($this->drupalCreateUser([
'view media',
'create media',
]));
$this->drupalGet('media/add');
$page->fillField('name[0][value]', $this->randomMachineName());
$page->fillField('field_media_test[0][value]', $this->randomString());
$page->pressButton('Save');
$media_id = $this->container->get('entity_type.manager')
->getStorage('media')
->getQuery()
->accessCheck(FALSE)
->execute();
$media_id = reset($media_id);
$assert_session->addressEquals("media/$media_id/edit");
// Test a redirect to the media overview for a user with the 'access media
// overview' permission.
$this->drupalLogin($this->drupalCreateUser([
'view media',
'create media',
'access media overview',
]));
$this->drupalGet('media/add');
$page->fillField('name[0][value]', $this->randomMachineName());
$page->fillField('field_media_test[0][value]', $this->randomString());
$page->pressButton('Save');
$assert_session->addressEquals('admin/content/media');
}
/**
* Tests the media collection route.
*/
public function testMediaCollectionRoute(): void {
/** @var \Drupal\Core\Entity\EntityStorageInterface $media_storage */
$media_storage = $this->container->get('entity_type.manager')->getStorage('media');
$this->container->get('module_installer')->uninstall(['views']);
// Create a media type and media item.
$media_type = $this->createMediaType('test');
$media = $media_storage->create([
'bundle' => $media_type->id(),
'name' => 'Unnamed',
]);
$media->save();
$this->drupalGet($media->toUrl('collection'));
$assert_session = $this->assertSession();
// Media list table exists.
$assert_session->elementExists('css', 'th:contains("Media Name")');
$assert_session->elementExists('css', 'th:contains("Type")');
$assert_session->elementExists('css', 'th:contains("Operations")');
// Media item is present.
$assert_session->elementExists('css', 'td:contains("Unnamed")');
}
}

View File

@ -0,0 +1,356 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
use Behat\Mink\Element\NodeElement;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Url;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\Tests\field_ui\Traits\FieldUiTestTrait;
/**
* Ensures that media UI works correctly.
*
* @group media
* @group #slow
*/
class MediaUiReferenceWidgetTest extends MediaFunctionalTestBase {
use FieldUiTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'block',
'media_test_source',
'media',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->drupalPlaceBlock('local_actions_block');
$this->drupalPlaceBlock('local_tasks_block');
}
/**
* Data provider for testMediaReferenceWidget().
*
* @return array[]
* Test data. See testMediaReferenceWidget() for the child array structure.
*/
public static function providerTestMediaReferenceWidget() {
return [
// Single-value fields with a single media type and the default widget:
// - The user can create and list the media.
'single_value:single_type:create_list' => [1, [TRUE], TRUE],
// - The user can list but not create the media.
'single_value:single_type:list' => [1, [FALSE], TRUE],
// - The user can create but not list the media.
'single_value:single_type:create' => [1, [TRUE], FALSE],
// - The user can neither create nor list the media.
'single_value:single_type' => [1, [FALSE], FALSE],
// Single-value fields with the tags-style widget:
// - The user can create and list the media.
'single_value:single_type:create_list:tags' => [1, [TRUE], TRUE, 'entity_reference_autocomplete_tags'],
// - The user can list but not create the media.
'single_value:single_type:list:tags' => [1, [FALSE], TRUE, 'entity_reference_autocomplete_tags'],
// - The user can create but not list the media.
'single_value:single_type:create:tags' => [1, [TRUE], FALSE, 'entity_reference_autocomplete_tags'],
// - The user can neither create nor list the media.
'single_value:single_type:tags' => [1, [FALSE], FALSE, 'entity_reference_autocomplete_tags'],
// Single-value fields with two media types:
// - The user can create both types.
'single_value:two_type:create2_list' => [1, [TRUE, TRUE], TRUE],
// - The user can create only one type.
'single_value:two_type:create1_list' => [1, [TRUE, FALSE], TRUE],
// - The user cannot create either type.
'single_value:two_type:list' => [1, [FALSE, FALSE], TRUE],
// Multiple-value field with a cardinality of 3, with media the user can
// create and list.
'multi_value:single_type:create_list' => [3, [TRUE], TRUE],
// The same, with the tags field.
'multi-value:single_type:create_list:tags' => [3, [TRUE], TRUE, 'entity_reference_autocomplete_tags'],
// Unlimited value field.
'unlimited_value:single_type:create_list' => [FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED, [TRUE], TRUE],
// Unlimited value field with the tags widget.
'unlimited_value:single_type:create_list:tags' => [FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED, [TRUE], TRUE, 'entity_reference_autocomplete_tags'],
];
}
/**
* Tests the default autocomplete widgets for media reference fields.
*
* @param int $cardinality
* The field cardinality.
* @param bool[] $media_type_create_access
* An array of booleans indicating whether to grant the test user create
* access for each media type. A media type is created automatically for
* each; for example, an array [TRUE, FALSE] would create two media types,
* one that allows the user to create media and a second that does not.
* @param bool $list_access
* Whether to grant the test user access to list media.
* @param string $widget_id
* The widget ID to test.
*
* @see media_field_widget_entity_reference_autocomplete_form_alter()
* @see media_field_widget_multiple_entity_reference_autocomplete_form_alter()
*
* @dataProvider providerTestMediaReferenceWidget
*/
public function testMediaReferenceWidget($cardinality, array $media_type_create_access, $list_access, $widget_id = 'entity_reference_autocomplete'): void {
$assert_session = $this->assertSession();
// Create two content types.
$non_media_content_type = $this->createContentType();
$content_type = $this->createContentType();
// Create some media types.
$media_types = [];
$permissions = [];
$create_media_types = [];
foreach ($media_type_create_access as $id => $access) {
if ($access) {
$create_media_types[] = "media_type_$id";
$permissions[] = "create media_type_$id media";
}
$this->createMediaType('test', [
'id' => "media_type_$id",
'label' => "media_type_$id",
]);
$media_types["media_type_$id"] = "media_type_$id";
}
// Create a user that can create content of the type, with other
// permissions as given by the data provider.
$permissions[] = "create {$content_type->id()} content";
if ($list_access) {
$permissions[] = "access media overview";
}
$test_user = $this->drupalCreateUser($permissions);
// Create a non-media entity reference.
$non_media_storage = FieldStorageConfig::create([
'field_name' => 'field_not_a_media_field',
'entity_type' => 'node',
'type' => 'entity_reference',
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
'settings' => [
'target_type' => 'node',
],
]);
$non_media_storage->save();
$non_media_field = FieldConfig::create([
'label' => 'No media here!',
'field_storage' => $non_media_storage,
'entity_type' => 'node',
'bundle' => $non_media_content_type->id(),
'settings' => [
'handler' => 'default',
'handler_settings' => [
'target_bundles' => [
$non_media_content_type->id() => $non_media_content_type->id(),
],
],
],
]);
$non_media_field->save();
\Drupal::entityTypeManager()
->getStorage('entity_form_display')
->load('node.' . $non_media_content_type->id() . '.default')
->setComponent('field_not_a_media_field', [
'type' => $widget_id,
])
->save();
// Create a media field through the user interface to ensure that the
// help text handling does not break the default value entry on the field
// settings form.
// Using submitForm() to avoid dealing with JavaScript on the previous
// page in the field creation.
$field_edit = [];
foreach ($media_types as $type) {
$field_edit["settings[handler_settings][target_bundles][$type]"] = TRUE;
}
$this->fieldUIAddNewField("admin/structure/types/manage/{$content_type->id()}", 'media_reference', "Media (cardinality $cardinality)", 'field_ui:entity_reference:media', [], $field_edit);
\Drupal::entityTypeManager()
->getStorage('entity_form_display')
->load('node.' . $content_type->id() . '.default')
->setComponent('field_media_reference', [
'type' => $widget_id,
])
->save();
// Some of the expected texts.
$create_help = 'Create your media on the media add page (opens a new window), then add it by name to the field below.';
$list_text = 'See the media list (opens a new window) to help locate media.';
$use_help = 'Type part of the media name.';
$create_header = "Create new media";
$use_header = "Use existing media";
// First check that none of the help texts are on the non-media content.
$this->drupalGet("/node/add/{$non_media_content_type->id()}");
$this->assertNoHelpTexts([
$create_header,
$create_help,
$use_header,
$use_help,
$list_text,
'Allowed media types:',
]);
// Now, check that the widget displays the expected help text under the
// given conditions for the test user.
$this->drupalLogin($test_user);
$this->drupalGet("/node/add/{$content_type->id()}");
// Specific expected help texts for the media field.
$create_header = "Create new media";
$use_header = "Use existing media";
$type_list = 'Allowed media types: ' . implode(", ", array_keys($media_types));
$fieldset_selector = '#edit-field-media-reference-wrapper fieldset';
$fieldset = $assert_session->elementExists('css', $fieldset_selector);
$this->assertSame("Media (cardinality $cardinality)", $assert_session->elementExists('css', 'legend', $fieldset)->getText());
// Assert text that should be displayed regardless of other access.
$this->assertHelpTexts([$use_header, $use_help, $type_list], $fieldset_selector);
// The entire section for creating new media should only be displayed if
// the user can create at least one media of the type.
if ($create_media_types) {
if (count($create_media_types) === 1) {
$url = Url::fromRoute('entity.media.add_form')->setRouteParameter('media_type', $create_media_types[0]);
}
else {
$url = Url::fromRoute('entity.media.add_page');
}
$this->assertHelpTexts([$create_header, $create_help], $fieldset_selector);
$this->assertHelpLink(
$fieldset,
'media add page',
[
'target' => '_blank',
'href' => $url->toString(),
]
);
}
else {
$this->assertNoHelpTexts([$create_header, $create_help]);
$this->assertNoHelpLink($fieldset, 'media add page');
}
if ($list_access) {
$this->assertHelpTexts([$list_text], $fieldset_selector);
$this->assertHelpLink(
$fieldset,
'media list',
[
'target' => '_blank',
'href' => Url::fromRoute('entity.media.collection')->toString(),
]
);
}
else {
$this->assertNoHelpTexts([$list_text]);
$this->assertNoHelpLink($fieldset, 'media list');
}
}
/**
* Asserts that the given texts are present exactly once.
*
* @param string[] $texts
* A list of the help texts to check.
* @param string $selector
* (optional) The selector to search.
*
* @internal
*/
protected function assertHelpTexts(array $texts, string $selector = ''): void {
$assert_session = $this->assertSession();
foreach ($texts as $text) {
// We only want to escape single quotes, so use str_replace() rather than
// addslashes().
$text = str_replace("'", "\'", $text);
if ($selector) {
$assert_session->elementsCount('css', $selector . ":contains('$text')", 1);
}
else {
$assert_session->pageTextContains($text);
}
}
}
/**
* Asserts that none of the given texts are present.
*
* @param string[] $texts
* A list of the help texts to check.
*
* @internal
*/
protected function assertNoHelpTexts(array $texts): void {
$assert_session = $this->assertSession();
foreach ($texts as $text) {
$assert_session->pageTextNotContains($text);
}
}
/**
* Asserts whether a given link is present.
*
* @param \Behat\Mink\Element\NodeElement $element
* The element to search.
* @param string $text
* The link text.
* @param string[] $attributes
* An associative array of any expected attributes, keyed by the
* attribute name.
*
* @internal
*/
protected function assertHelpLink(NodeElement $element, string $text, array $attributes = []): void {
// Find all the links inside the element.
$link = $element->findLink($text);
$this->assertNotEmpty($link);
foreach ($attributes as $attribute => $value) {
$this->assertSame($link->getAttribute($attribute), $value);
}
}
/**
* Asserts that a given link is not present.
*
* @param \Behat\Mink\Element\NodeElement $element
* The element to search.
* @param string $text
* The link text.
*
* @internal
*/
protected function assertNoHelpLink(NodeElement $element, string $text): void {
$assert_session = $this->assertSession();
// Assert that the link and its text are not present anywhere on the page.
$assert_session->elementNotExists('named', ['link', $text], $element);
$assert_session->pageTextNotContains($text);
}
}

View File

@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
use Drupal\media\OEmbed\Resource;
use Drupal\Tests\media\Traits\OEmbedTestTrait;
// cspell:ignore dailymotion Schipulcon
/**
* Tests the oEmbed resource fetcher service.
*
* @coversDefaultClass \Drupal\media\OEmbed\ResourceFetcher
*
* @group media
*/
class ResourceFetcherTest extends MediaFunctionalTestBase {
use OEmbedTestTrait;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->useFixtureProviders();
$this->lockHttpClientToFixtures();
}
/**
* Data provider for testFetchResource().
*
* @return array
* An array of test data.
*/
public static function providerFetchResource() {
return [
'JSON resource' => [
'video_vimeo.json',
'Vimeo',
'Drupal Rap Video - Schipulcon09',
],
'XML resource' => [
'video_dailymotion.xml',
'Dailymotion',
"#d8rules - Support the Rules module for Drupal 8",
],
];
}
/**
* Tests resource fetching.
*
* @param string $resource_url
* The URL of the resource to fetch, relative to the base URL.
* @param string $provider_name
* The expected name of the resource provider.
* @param string $title
* The expected title of the resource.
*
* @covers ::fetchResource
*
* @dataProvider providerFetchResource
*/
public function testFetchResource($resource_url, $provider_name, $title): void {
/** @var \Drupal\media\OEmbed\Resource $resource */
$resource = $this->container->get('media.oembed.resource_fetcher')
->fetchResource($resource_url);
$this->assertInstanceOf(Resource::class, $resource);
$this->assertSame($provider_name, $resource->getProvider()->getName());
$this->assertSame($title, $resource->getTitle());
}
}

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional\Rest;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
/**
* @group rest
*/
class MediaJsonAnonTest extends MediaResourceTestBase {
use AnonResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional\Rest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group rest
*/
class MediaJsonBasicAuthTest extends MediaResourceTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional\Rest;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
/**
* @group rest
*/
class MediaJsonCookieTest extends MediaResourceTestBase {
use CookieResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@ -0,0 +1,488 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional\Rest;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Url;
use Drupal\file\Entity\File;
use Drupal\media\Entity\Media;
use Drupal\media\Entity\MediaType;
use Drupal\rest\RestResourceConfigInterface;
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
use Drupal\user\Entity\Role;
use Drupal\user\Entity\User;
use Drupal\user\RoleInterface;
use GuzzleHttp\RequestOptions;
/**
* Resource test base for the media entity.
*/
abstract class MediaResourceTestBase extends EntityResourceTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['content_translation', 'media'];
/**
* {@inheritdoc}
*/
protected static $entityTypeId = 'media';
/**
* @var \Drupal\media\MediaInterface
*/
protected $entity;
/**
* {@inheritdoc}
*/
protected static $patchProtectedFieldNames = [
'changed' => NULL,
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
\Drupal::configFactory()
->getEditable('media.settings')
->set('standalone_url', TRUE)
->save(TRUE);
// Provisioning the Media REST resource without the File REST resource does
// not make sense.
$this->resourceConfigStorage->create([
'id' => 'entity.file',
'granularity' => RestResourceConfigInterface::RESOURCE_GRANULARITY,
'configuration' => [
'methods' => ['GET'],
'formats' => [static::$format],
'authentication' => isset(static::$auth) ? [static::$auth] : [],
],
'status' => TRUE,
])->save();
$this->container->get('router.builder')->rebuild();
}
/**
* {@inheritdoc}
*/
protected function setUpAuthorization($method) {
switch ($method) {
case 'GET':
$this->grantPermissionsToTestedRole(['view media']);
break;
case 'POST':
$this->grantPermissionsToTestedRole(['create camelids media', 'access content']);
break;
case 'PATCH':
$this->grantPermissionsToTestedRole(['edit any camelids media']);
// @todo Remove this in https://www.drupal.org/node/2824851.
$this->grantPermissionsToTestedRole(['access content']);
break;
case 'DELETE':
$this->grantPermissionsToTestedRole(['delete any camelids media']);
break;
}
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
if (!MediaType::load('camelids')) {
// Create a "Camelids" media type.
$media_type = MediaType::create([
'label' => 'Camelids',
'id' => 'camelids',
'description' => 'Camelids are large, strictly herbivorous animals with slender necks and long legs.',
'source' => 'file',
]);
$media_type->save();
// Create the source field.
$source_field = $media_type->getSource()->createSourceField($media_type);
$source_field->getFieldStorageDefinition()->save();
$source_field->save();
$media_type
->set('source_configuration', [
'source_field' => $source_field->getName(),
])
->save();
}
// Create a file to upload.
$file = File::create([
'uri' => 'public://llama.txt',
]);
$file->setPermanent();
$file->save();
// Create a "Llama" media item.
$media = Media::create([
'bundle' => 'camelids',
'field_media_file' => [
'target_id' => $file->id(),
],
]);
$media
->setName('Llama')
->setPublished()
->setCreatedTime(123456789)
->setOwnerId(static::$auth ? $this->account->id() : 0)
->setRevisionUserId(static::$auth ? $this->account->id() : 0)
->save();
return $media;
}
/**
* {@inheritdoc}
*/
protected function getExpectedNormalizedEntity() {
$file = File::load(1);
$thumbnail = File::load(2);
$author = User::load($this->entity->getOwnerId());
return [
'mid' => [
[
'value' => 1,
],
],
'uuid' => [
[
'value' => $this->entity->uuid(),
],
],
'vid' => [
[
'value' => 1,
],
],
'langcode' => [
[
'value' => 'en',
],
],
'bundle' => [
[
'target_id' => 'camelids',
'target_type' => 'media_type',
'target_uuid' => MediaType::load('camelids')->uuid(),
],
],
'name' => [
[
'value' => 'Llama',
],
],
'field_media_file' => [
[
'description' => NULL,
'display' => NULL,
'target_id' => (int) $file->id(),
'target_type' => 'file',
'target_uuid' => $file->uuid(),
'url' => $file->createFileUrl(FALSE),
],
],
'thumbnail' => [
[
'alt' => '',
'width' => 180,
'height' => 180,
'target_id' => (int) $thumbnail->id(),
'target_type' => 'file',
'target_uuid' => $thumbnail->uuid(),
'title' => NULL,
'url' => $thumbnail->createFileUrl(FALSE),
],
],
'status' => [
[
'value' => TRUE,
],
],
'created' => [
[
'value' => (new \DateTime())->setTimestamp(123456789)->setTimezone(new \DateTimeZone('UTC'))->format(\DateTime::RFC3339),
'format' => \DateTime::RFC3339,
],
],
'changed' => [
[
'value' => (new \DateTime())->setTimestamp((int) $this->entity->getChangedTime())->setTimezone(new \DateTimeZone('UTC'))->format(\DateTime::RFC3339),
'format' => \DateTime::RFC3339,
],
],
'revision_created' => [
[
'value' => (new \DateTime())->setTimestamp((int) $this->entity->getRevisionCreationTime())->setTimezone(new \DateTimeZone('UTC'))->format(\DateTime::RFC3339),
'format' => \DateTime::RFC3339,
],
],
'default_langcode' => [
[
'value' => TRUE,
],
],
'uid' => [
[
'target_id' => (int) $author->id(),
'target_type' => 'user',
'target_uuid' => $author->uuid(),
'url' => base_path() . 'user/' . $author->id(),
],
],
'revision_user' => [
[
'target_id' => (int) $author->id(),
'target_type' => 'user',
'target_uuid' => $author->uuid(),
'url' => base_path() . 'user/' . $author->id(),
],
],
'revision_translation_affected' => [
[
'value' => TRUE,
],
],
];
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPostEntity() {
return [
'bundle' => [
[
'target_id' => 'camelids',
],
],
'name' => [
[
'value' => 'Drama llama',
],
],
'field_media_file' => [
[
'description' => NULL,
'display' => NULL,
'target_id' => 3,
],
],
];
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPatchEntity() {
return array_diff_key($this->getNormalizedPostEntity(), ['field_media_file' => TRUE]);
}
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedAccessMessage($method) {
switch ($method) {
case 'GET':
return "The 'view media' permission is required when the media item is published.";
case 'POST':
return "The following permissions are required: 'administer media' OR 'create media' OR 'create camelids media'.";
case 'PATCH':
return "The following permissions are required: 'update any media' OR 'update own media' OR 'camelids: edit any media' OR 'camelids: edit own media'.";
case 'DELETE':
return "The following permissions are required: 'delete any media' OR 'delete own media' OR 'camelids: delete any media' OR 'camelids: delete own media'.";
default:
return parent::getExpectedUnauthorizedAccessMessage($method);
}
}
/**
* {@inheritdoc}
*/
protected function getExpectedCacheContexts() {
return [
'languages:language_interface',
'url.site',
'user.permissions',
];
}
/**
* {@inheritdoc}
*/
public function testPost(): void {
$file_storage = $this->container->get('entity_type.manager')->getStorage('file');
// Step 1: upload file, results in File entity marked temporary.
$this->uploadFile();
$file = $file_storage->loadUnchanged(3);
$this->assertTrue($file->isTemporary());
$this->assertFalse($file->isPermanent());
// Step 2: create Media entity using the File, makes File entity permanent.
parent::testPost();
$file = $file_storage->loadUnchanged(3);
$this->assertFalse($file->isTemporary());
$this->assertTrue($file->isPermanent());
}
/**
* Tests the 'file_upload' REST resource plugin.
*
* This test duplicates some of the 'file_upload' REST resource plugin test
* coverage.
*
* @see \Drupal\Tests\rest\Functional\FileUploadResourceTestBase
*/
protected function uploadFile() {
// Enable the 'file_upload' REST resource for the current format + auth.
$this->resourceConfigStorage->create([
'id' => 'file.upload',
'granularity' => RestResourceConfigInterface::RESOURCE_GRANULARITY,
'configuration' => [
'methods' => ['POST'],
'formats' => [static::$format],
'authentication' => isset(static::$auth) ? [static::$auth] : [],
],
'status' => TRUE,
])->save();
$this->refreshTestStateAfterRestConfigChange();
$this->initAuthentication();
// POST to create a File entity.
$url = Url::fromUri('base:file/upload/media/camelids/field_media_file');
$url->setOption('query', ['_format' => static::$format]);
$request_options = [];
$request_options[RequestOptions::HEADERS] = [
// Set the required (and only accepted) content type for the request.
'Content-Type' => 'application/octet-stream',
// Set the required Content-Disposition header for the file name.
'Content-Disposition' => 'file; filename="drupal rocks 🤘.txt"',
];
$request_options[RequestOptions::BODY] = 'Drupal is the best!';
$request_options = NestedArray::mergeDeep($request_options, $this->getAuthenticationRequestOptions('POST'));
$response = $this->request('POST', $url, $request_options);
$this->assertResourceErrorResponse(403, $this->getExpectedUnauthorizedAccessMessage('POST'), $response);
// Grant necessary permission, retry.
$this->grantPermissionsToTestedRole(['create camelids media']);
$response = $this->request('POST', $url, $request_options);
$this->assertSame(201, $response->getStatusCode());
$expected = $this->getExpectedNormalizedFileEntity();
static::recursiveKSort($expected);
$actual = $this->serializer->decode((string) $response->getBody(), static::$format);
static::recursiveKSort($actual);
$this->assertSame($expected, $actual);
// Make sure the role save below properly invalidates cache tags.
$this->refreshVariables();
// To still run the complete test coverage for POSTing a Media entity, we
// must revoke the additional permissions that we granted.
$role = Role::load(static::$auth ? RoleInterface::AUTHENTICATED_ID : RoleInterface::ANONYMOUS_ID);
$role->revokePermission('create camelids media');
$role->trustData()->save();
}
/**
* Gets the expected file entity.
*
* @return array
* The expected normalized data array.
*/
protected function getExpectedNormalizedFileEntity() {
$file = File::load(3);
$owner = static::$auth ? $this->account : User::load(0);
return [
'fid' => [
[
'value' => 3,
],
],
'uuid' => [
[
'value' => $file->uuid(),
],
],
'langcode' => [
[
'value' => 'en',
],
],
'uid' => [
[
'target_id' => (int) $owner->id(),
'target_type' => 'user',
'target_uuid' => $owner->uuid(),
'url' => base_path() . 'user/' . $owner->id(),
],
],
'filename' => [
[
'value' => 'drupal rocks 🤘.txt',
],
],
'uri' => [
[
'value' => 'public://' . date('Y-m') . '/drupal rocks 🤘.txt',
'url' => base_path() . $this->siteDirectory . '/files/' . date('Y-m') . '/drupal%20rocks%20%F0%9F%A4%98.txt',
],
],
'filemime' => [
[
'value' => 'text/plain',
],
],
'filesize' => [
[
'value' => 19,
],
],
'status' => [
[
'value' => FALSE,
],
],
'created' => [
[
'value' => (new \DateTime())->setTimestamp($file->getCreatedTime())->setTimezone(new \DateTimeZone('UTC'))->format(\DateTime::RFC3339),
'format' => \DateTime::RFC3339,
],
],
'changed' => [
[
'value' => (new \DateTime())->setTimestamp($file->getChangedTime())->setTimezone(new \DateTimeZone('UTC'))->format(\DateTime::RFC3339),
'format' => \DateTime::RFC3339,
],
],
];
}
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedEntityAccessCacheability($is_authenticated) {
// @see \Drupal\media\MediaAccessControlHandler::checkAccess()
return parent::getExpectedUnauthorizedEntityAccessCacheability($is_authenticated)
->addCacheTags(['media:1']);
}
}

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional\Rest;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
/**
* @group rest
*/
class MediaTypeJsonAnonTest extends MediaTypeResourceTestBase {
use AnonResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional\Rest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group rest
*/
class MediaTypeJsonBasicAuthTest extends MediaTypeResourceTestBase {
use BasicAuthResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View File

@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional\Rest;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
/**
* @group rest
*/
class MediaTypeJsonCookieTest extends MediaTypeResourceTestBase {
use CookieResourceTestTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'json';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'application/json';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional\Rest;
use Drupal\media\Entity\MediaType;
use Drupal\Tests\rest\Functional\EntityResource\ConfigEntityResourceTestBase;
/**
* Resource test base for the MediaType entity.
*/
abstract class MediaTypeResourceTestBase extends ConfigEntityResourceTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['media'];
/**
* {@inheritdoc}
*/
protected static $entityTypeId = 'media_type';
/**
* @var \Drupal\media\MediaTypeInterface
*/
protected $entity;
/**
* {@inheritdoc}
*/
protected function setUpAuthorization($method) {
$this->grantPermissionsToTestedRole(['administer media types']);
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
// Create a "Camelids" media type.
$camelids = MediaType::create([
'label' => 'Camelids',
'id' => 'camelids',
'description' => 'Camelids are large, strictly herbivorous animals with slender necks and long legs.',
'source' => 'file',
]);
$camelids->save();
return $camelids;
}
/**
* {@inheritdoc}
*/
protected function getExpectedNormalizedEntity() {
return [
'dependencies' => [],
'description' => 'Camelids are large, strictly herbivorous animals with slender necks and long legs.',
'field_map' => [],
'id' => 'camelids',
'label' => 'Camelids',
'langcode' => 'en',
'source' => 'file',
'queue_thumbnail_downloads' => FALSE,
'new_revision' => FALSE,
'source_configuration' => [
'source_field' => '',
],
'status' => TRUE,
'uuid' => $this->entity->uuid(),
];
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPostEntity() {
// @todo Update in https://www.drupal.org/node/2300677.
return [];
}
}

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional\Rest;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class MediaTypeXmlAnonTest extends MediaTypeResourceTestBase {
use AnonResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional\Rest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class MediaTypeXmlBasicAuthTest extends MediaTypeResourceTestBase {
use BasicAuthResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View File

@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional\Rest;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class MediaTypeXmlCookieTest extends MediaTypeResourceTestBase {
use CookieResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional\Rest;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class MediaXmlAnonTest extends MediaResourceTestBase {
use AnonResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional\Rest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class MediaXmlBasicAuthTest extends MediaResourceTestBase {
use BasicAuthResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['basic_auth'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected static $auth = 'basic_auth';
}

View File

@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional\Rest;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class MediaXmlCookieTest extends MediaResourceTestBase {
use CookieResourceTestTrait;
use XmlEntityNormalizationQuirksTrait;
/**
* {@inheritdoc}
*/
protected static $format = 'xml';
/**
* {@inheritdoc}
*/
protected static $mimeType = 'text/xml; charset=UTF-8';
/**
* {@inheritdoc}
*/
protected static $auth = 'cookie';
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
}

View File

@ -0,0 +1,143 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Functional;
use Drupal\Tests\media\Traits\OEmbedTestTrait;
// cspell:ignore dailymotion
/**
* Tests the oEmbed URL resolver service.
*
* @coversDefaultClass \Drupal\media\OEmbed\UrlResolver
*
* @group media
*/
class UrlResolverTest extends MediaFunctionalTestBase {
use OEmbedTestTrait;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->lockHttpClientToFixtures();
$this->useFixtureProviders();
}
/**
* Data provider for testEndpointMatching().
*
* @see ::testEndpointMatching()
*
* @return array
* An array of test data.
*/
public static function providerEndpointMatching() {
return [
'match by endpoint: Twitter' => [
'https://twitter.com/Dries/status/999985431595880448',
'https://publish.twitter.com/oembed?url=https%3A//twitter.com/Dries/status/999985431595880448',
],
'match by endpoint: Vimeo' => [
'https://vimeo.com/14782834',
'https://vimeo.com/api/oembed.json?url=https%3A//vimeo.com/14782834',
],
'match by endpoint: Dailymotion' => [
'https://www.dailymotion.com/video/x2vzluh',
'https://www.dailymotion.com/services/oembed?url=https%3A//www.dailymotion.com/video/x2vzluh',
],
'match by endpoint: Facebook' => [
'https://www.facebook.com/facebook/videos/10153231379946729/',
'https://www.facebook.com/plugins/video/oembed.json?url=https%3A//www.facebook.com/facebook/videos/10153231379946729/',
],
];
}
/**
* Tests resource URL resolution with a matched provider endpoint.
*
* @param string $url
* The asset URL to resolve.
* @param string $resource_url
* The expected oEmbed resource URL of the asset.
*
* @covers ::getProviderByUrl
* @covers ::getResourceUrl
* @dataProvider providerEndpointMatching
*/
public function testEndpointMatching($url, $resource_url): void {
$this->assertSame(
$resource_url,
$this->container->get('media.oembed.url_resolver')->getResourceUrl($url)
);
}
/**
* Tests that hook_oembed_resource_url_alter() is invoked.
*
* @depends testEndpointMatching
*/
public function testResourceUrlAlterHook(): void {
$this->container->get('module_installer')->install(['media_test_oembed']);
// Much like FunctionalTestSetupTrait::installModulesFromClassProperty()
// after module install the rebuilt container needs to be used.
$this->container = \Drupal::getContainer();
$resource_url = $this->container->get('media.oembed.url_resolver')
->getResourceUrl('https://vimeo.com/14782834');
$this->assertStringContainsString('altered=1', parse_url($resource_url, PHP_URL_QUERY));
}
/**
* Data provider for testUrlDiscovery().
*
* @see ::testUrlDiscovery()
*
* @return array
* An array of test data.
*/
public static function providerUrlDiscovery() {
return [
'JSON resource' => [
'video_vimeo.html',
'https://vimeo.com/api/oembed.json?url=video_vimeo.html',
],
'XML resource' => [
'video_dailymotion.html',
'https://www.dailymotion.com/services/oembed?url=video_dailymotion.html',
],
];
}
/**
* Tests URL resolution when the URL is discovered by scanning the asset.
*
* @param string $url
* The asset URL to resolve.
* @param string $resource_url
* The expected oEmbed resource URL of the asset.
*
* @covers ::discoverResourceUrl
* @covers ::getProviderByUrl
* @covers ::getResourceUrl
*
* @dataProvider providerUrlDiscovery
*/
public function testUrlDiscovery($url, $resource_url): void {
$this->assertSame(
$this->container->get('media.oembed.url_resolver')->getResourceUrl($url),
$resource_url
);
}
}

View File

@ -0,0 +1,208 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\FunctionalJavascript;
use Drupal\Core\Config\FileStorage;
use Drupal\Core\Config\InstallStorage;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\media\Entity\Media;
use Drupal\node\Entity\Node;
use Drupal\node\Entity\NodeType;
/**
* Basic display tests for Media.
*
* @group media
*/
class MediaDisplayTest extends MediaJavascriptTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Install the optional configs from the standard profile.
$extension_path = $this->container->get('extension.list.profile')->getPath('standard');
$optional_install_path = $extension_path . '/' . InstallStorage::CONFIG_OPTIONAL_DIRECTORY;
$storage = new FileStorage($optional_install_path);
$this->container->get('config.installer')->installOptionalConfig($storage, '');
// Reset all the static caches and list caches.
$this->container->get('config.factory')->reset();
// This test is going to test the display, so we need the standalone URL.
\Drupal::configFactory()
->getEditable('media.settings')
->set('standalone_url', TRUE)
->save(TRUE);
$this->container->get('router.builder')->rebuild();
}
/**
* Tests basic media display.
*/
public function testMediaDisplay(): void {
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
$media_type = $this->createMediaType('test');
// Create a media item.
$media = Media::create([
'bundle' => $media_type->id(),
'name' => 'Fantastic!',
]);
$media->save();
$this->drupalGet('media/' . $media->id());
// Verify the "name" field is really not present. The name should be in the
// h1 with no additional markup in the h1.
$assert_session->elementTextContains('css', 'h1', $media->getName());
$assert_session->elementNotExists('css', 'h1 div');
// Enable the field on the display and verify it becomes visible on the UI.
$this->drupalGet("/admin/structure/media/manage/{$media_type->id()}/display");
$assert_session->buttonExists('Show row weights')->press();
$this->assertSession()->waitForElementVisible('css', '[name="fields[name][region]"]');
$page->selectFieldOption('fields[name][region]', 'content');
$assert_session->waitForElementVisible('css', '#edit-fields-name-settings-edit');
$page->pressButton('Save');
$this->drupalGet('media/' . $media->id());
// Verify the name is present, and its text matches what is expected. Now
// there should be markup in the h1.
$assert_session->elementTextContains('xpath', '//h1/div/div[1]', 'Name');
$assert_session->elementTextContains('xpath', '//h1/div/div[2]', $media->getName());
// In the standard profile, there are some pre-cooked types. Make sure the
// elements configured on their displays are the expected ones.
$this->drupalGet('media/add/image');
$image_media_name = 'example_1.jpeg';
$page->attachFileToField('files[field_media_image_0]', $this->root . '/core/modules/media/tests/fixtures/' . $image_media_name);
$result = $assert_session->waitForButton('Remove');
$this->assertNotEmpty($result);
$page->fillField('field_media_image[0][alt]', 'Image Alt Text 1');
$page->pressButton('Save');
$image_media_id = $this->container
->get('entity_type.manager')
->getStorage('media')
->getQuery()
->accessCheck(FALSE)
->sort('mid', 'DESC')
->execute();
$image_media_id = reset($image_media_id);
// Go to the media entity view.
$this->drupalGet('/media/' . $image_media_id);
// Check if the default media name is generated as expected.
$assert_session->elementTextContains('xpath', '//h1', $image_media_name);
// Here we expect to see only the image, nothing else.
// Assert only one element in the content region.
$media_item = $assert_session->elementExists('xpath', '//div[@class="layout-content"]/div/div[2]');
$assert_session->elementsCount('xpath', '/div', 1, $media_item);
// Assert the image is present inside the media element.
$media_image = $assert_session->elementExists('xpath', '//img', $media_item);
// Assert that the image src uses the large image style, the label is
// visually hidden, and there is no link to the image file.
/** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */
$file_url_generator = \Drupal::service('file_url_generator');
$expected_image_src = $file_url_generator->generate(\Drupal::token()->replace('public://styles/large/public/[date:custom:Y]-[date:custom:m]/example_1.jpeg'))->toString();
$this->assertStringContainsString($expected_image_src, $media_image->getAttribute('src'));
$field = $assert_session->elementExists('xpath', '/div[1]', $media_item);
$assert_session->elementExists('xpath', '/div[@class="visually-hidden"]', $field);
$assert_session->elementNotExists('xpath', '//a', $field);
$test_filename = $this->randomMachineName() . '.txt';
$test_filepath = 'public://' . $test_filename;
file_put_contents($test_filepath, $this->randomMachineName());
$this->drupalGet("media/add/document");
$page->attachFileToField("files[field_media_document_0]", \Drupal::service('file_system')->realpath($test_filepath));
$result = $assert_session->waitForButton('Remove');
$this->assertNotEmpty($result);
$page->pressButton('Save');
// Go to the media entity view.
$this->drupalGet($this->assertLinkToCreatedMedia());
// Check if the default media name is generated as expected.
$assert_session->elementTextContains('css', 'h1', $test_filename);
// Here we expect to see only the linked filename.
// Assert only one element in the content region.
$media_item = $assert_session->elementExists('xpath', '//div[@class="layout-content"]/div/div[2]');
$assert_session->elementsCount('xpath', '/div', 1, $media_item);
// Assert the file link is present, and its text matches the filename.
$link = $assert_session->elementExists('xpath', '//a', $media_item);
$this->assertSame($test_filename, $link->getText());
// Create a node type "page" to use as host entity.
$node_type = NodeType::create([
'type' => 'page',
'name' => 'Page',
]);
$node_type->save();
// Reference the created media using an entity_reference field and make sure
// the output is what we expect.
$storage = FieldStorageConfig::create([
'entity_type' => 'node',
'field_name' => 'field_related_media',
'type' => 'entity_reference',
'settings' => [
'target_type' => 'media',
],
]);
$storage->save();
FieldConfig::create([
'field_storage' => $storage,
'entity_type' => 'node',
'bundle' => $node_type->id(),
'label' => 'Related media',
'settings' => [
'handler_settings' => [
'target_bundles' => [
'image' => 'image',
],
],
],
])->save();
\Drupal::service('entity_display.repository')->getViewDisplay('node', $node_type->id())
->setComponent('field_related_media', [
'type' => 'entity_reference_entity_view',
'label' => 'above',
'settings' => [
'view_mode' => 'full',
],
])->save();
$node = Node::create([
'title' => 'Host node',
'type' => $node_type->id(),
'field_related_media' => [
'target_id' => $image_media_id,
],
]);
$node->save();
$this->drupalGet('/node/' . $node->id());
// Media field (field_related_media) is there.
$assert_session->pageTextContains('Related media');
// Media name element is not there.
$assert_session->pageTextNotContains($image_media_name);
// Only one image is present.
$assert_session->elementsCount('xpath', '//img', 1);
// The image has the correct image style.
$assert_session->elementAttributeContains('xpath', '//img', 'src', '/styles/large/');
}
}

View File

@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\FunctionalJavascript;
/**
* @covers ::media_filter_format_edit_form_validate
* @group media
* @group #slow
*/
class MediaEmbedFilterConfigurationUiAddTest extends MediaEmbedFilterTestBase {
/**
* @covers \Drupal\media\Hook\MediaHooks::formFilterFormatAddFormAlter
* @dataProvider providerTestValidations
*/
public function testValidationWhenAdding($filter_html_status, $filter_align_status, $filter_caption_status, $filter_html_image_secure_status, $media_embed, $allowed_html, $expected_error_message): void {
$this->drupalGet('admin/config/content/formats/add');
// Enable the `filter_html` and `media_embed` filters.
$page = $this->getSession()->getPage();
$page->fillField('name', 'Another test format');
$this->showHiddenFields();
$page->findField('format')->setValue('another_media_embed_test');
if ($filter_html_status) {
$page->checkField('filters[filter_html][status]');
}
if ($filter_align_status) {
$page->checkField('filters[filter_align][status]');
}
if ($filter_caption_status) {
$page->checkField('filters[filter_caption][status]');
}
if ($filter_html_image_secure_status) {
$page->checkField('filters[filter_html_image_secure][status]');
}
if ($media_embed === TRUE || is_numeric($media_embed)) {
$page->checkField('filters[media_embed][status]');
// Set a non-default weight.
if (is_numeric($media_embed)) {
$this->click('.tabledrag-toggle-weight');
$page->selectFieldOption('filters[media_embed][weight]', $media_embed);
}
}
if (!empty($allowed_html)) {
$page->clickLink('Limit allowed HTML tags and correct faulty HTML');
$page->fillField('filters[filter_html][settings][allowed_html]', $allowed_html);
}
$page->pressButton('Save configuration');
if ($expected_error_message) {
$this->assertSession()->pageTextNotContains('Added text format Another test format.');
$this->assertSession()->pageTextContains($expected_error_message);
}
else {
$this->assertSession()->pageTextContains('Added text format Another test format.');
}
}
}

View File

@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\FunctionalJavascript;
/**
* @covers ::media_filter_format_edit_form_validate
* @group media
* @group #slow
*/
class MediaEmbedFilterConfigurationUiEditTest extends MediaEmbedFilterTestBase {
/**
* @covers \Drupal\media\Hook\MediaHooks::formFilterFormatEditFormAlter
* @dataProvider providerTestValidations
*/
public function testValidationWhenEditing($filter_html_status, $filter_align_status, $filter_caption_status, $filter_html_image_secure_status, $media_embed, $allowed_html, $expected_error_message): void {
$this->drupalGet('admin/config/content/formats/manage/media_embed_test');
// Enable the `filter_html` and `media_embed` filters.
$page = $this->getSession()->getPage();
if ($filter_html_status) {
$page->checkField('filters[filter_html][status]');
}
if ($filter_align_status) {
$page->checkField('filters[filter_align][status]');
}
if ($filter_caption_status) {
$page->checkField('filters[filter_caption][status]');
}
if ($filter_html_image_secure_status) {
$page->checkField('filters[filter_html_image_secure][status]');
}
if ($media_embed === TRUE || is_numeric($media_embed)) {
$page->checkField('filters[media_embed][status]');
// Set a non-default weight.
if (is_numeric($media_embed)) {
$this->click('.tabledrag-toggle-weight');
$page->selectFieldOption('filters[media_embed][weight]', $media_embed);
}
}
if (!empty($allowed_html)) {
$page->clickLink('Limit allowed HTML tags and correct faulty HTML');
$page->fillField('filters[filter_html][settings][allowed_html]', $allowed_html);
}
$page->pressButton('Save configuration');
if ($expected_error_message) {
$this->assertSession()->pageTextNotContains('The text format Test format has been updated.');
$this->assertSession()->pageTextContains($expected_error_message);
}
else {
$this->assertSession()->pageTextContains('The text format Test format has been updated.');
}
}
}

View File

@ -0,0 +1,176 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\FunctionalJavascript;
use Drupal\filter\Entity\FilterFormat;
/**
* Base class for media embed filter configuration tests.
*/
abstract class MediaEmbedFilterTestBase extends MediaJavascriptTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*
* @todo Remove this class property in https://www.drupal.org/node/3091878/.
*/
protected $failOnJavascriptConsoleErrors = FALSE;
/**
* {@inheritdoc}
*/
public static function setUpBeforeClass(): void {
parent::setUpBeforeClass();
// Necessary for @covers to work.
require_once __DIR__ . '/../../../media.module';
}
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$format = FilterFormat::create([
'format' => 'media_embed_test',
'name' => 'Test format',
'filters' => [],
]);
$format->save();
$this->drupalLogin($this->drupalCreateUser([
'administer filters',
$format->getPermissionName(),
]));
}
/**
* Data provider for testing validation when adding and editing media embeds.
*/
public static function providerTestValidations(): array {
return [
'Tests that no filter_html occurs when filter_html not enabled.' => [
'filter_html_status' => FALSE,
'filter_align_status' => FALSE,
'filter_caption_status' => FALSE,
'filter_html_image_secure_status' => FALSE,
'media_embed' => TRUE,
'allowed_html' => FALSE,
'expected_error_message' => FALSE,
],
'Tests validation when both filter_html and media_embed are disabled.' => [
'filter_html_status' => FALSE,
'filter_align_status' => FALSE,
'filter_caption_status' => FALSE,
'filter_html_image_secure_status' => FALSE,
'media_embed' => FALSE,
'allowed_html' => FALSE,
'expected_error_message' => FALSE,
],
'Tests validation when media_embed filter not enabled and filter_html is enabled.' => [
'filter_html_status' => TRUE,
'filter_align_status' => FALSE,
'filter_caption_status' => FALSE,
'filter_html_image_secure_status' => FALSE,
'media_embed' => FALSE,
'allowed_html' => 'default',
'expected_error_message' => FALSE,
],
'Tests validation when drupal-media element has no attributes.' => [
'filter_html_status' => TRUE,
'filter_align_status' => FALSE,
'filter_caption_status' => FALSE,
'filter_html_image_secure_status' => FALSE,
'media_embed' => TRUE,
'allowed_html' => "<a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type='1 A I'> <li> <dl> <dt> <dd> <h2 id='jump-*'> <h3 id> <h4 id> <h5 id> <h6 id> <drupal-media>",
'expected_error_message' => 'The <drupal-media> tag in the allowed HTML tags is missing the following attributes: data-entity-type, data-entity-uuid.',
],
'Tests validation when drupal-media element lacks some required attributes.' => [
'filter_html_status' => TRUE,
'filter_align_status' => FALSE,
'filter_caption_status' => FALSE,
'filter_html_image_secure_status' => FALSE,
'media_embed' => TRUE,
'allowed_html' => "<a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type='1 A I'> <li> <dl> <dt> <dd> <h2 id='jump-*'> <h3 id> <h4 id> <h5 id> <h6 id> <drupal-media data-entity-uuid data-align>",
'expected_error_message' => 'The <drupal-media> tag in the allowed HTML tags is missing the following attributes: data-entity-type.',
],
'Tests validation when both filter_html and media_embed are enabled and configured correctly' => [
'filter_html_status' => TRUE,
'filter_align_status' => FALSE,
'filter_caption_status' => FALSE,
'filter_html_image_secure_status' => FALSE,
'media_embed' => TRUE,
'allowed_html' => "<a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type='1 A I'> <li> <dl> <dt> <dd> <h2 id='jump-*'> <h3 id> <h4 id> <h5 id> <h6 id> <drupal-media data-entity-type data-entity-uuid data-view-mode>",
'expected_error_message' => FALSE,
],
'Order validation: media_embed before all filters' => [
'filter_html_status' => TRUE,
'filter_align_status' => TRUE,
'filter_caption_status' => TRUE,
'filter_html_image_secure_status' => TRUE,
'media_embed' => '-5',
'allowed_html' => "<a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type='1 A I'> <li> <dl> <dt> <dd> <h2 id='jump-*'> <h3 id> <h4 id> <h5 id> <h6 id> <drupal-media data-entity-type data-entity-uuid data-view-mode>",
'expected_error_message' => 'The Embed media filter needs to be placed after the following filters: Align images, Caption images, Restrict images to this site.',
],
'Order validation: media_embed before filter_align' => [
'filter_html_status' => FALSE,
'filter_align_status' => TRUE,
'filter_caption_status' => FALSE,
'filter_html_image_secure_status' => FALSE,
'media_embed' => '-5',
'allowed_html' => '',
'expected_error_message' => 'The Embed media filter needs to be placed after the Align images filter.',
],
'Order validation: media_embed before filter_caption' => [
'filter_html_status' => FALSE,
'filter_align_status' => FALSE,
'filter_caption_status' => TRUE,
'filter_html_image_secure_status' => FALSE,
'media_embed' => '-5',
'allowed_html' => '',
'expected_error_message' => 'The Embed media filter needs to be placed after the Caption images filter.',
],
'Order validation: media_embed before filter_html_image_secure' => [
'filter_html_status' => FALSE,
'filter_align_status' => FALSE,
'filter_caption_status' => FALSE,
'filter_html_image_secure_status' => TRUE,
'media_embed' => '-5',
'allowed_html' => '',
'expected_error_message' => 'The Embed media filter needs to be placed after the Restrict images to this site filter.',
],
'Order validation: media_embed after filter_align and filter_caption but before filter_html_image_secure' => [
'filter_html_status' => TRUE,
'filter_align_status' => TRUE,
'filter_caption_status' => TRUE,
'filter_html_image_secure_status' => TRUE,
'media_embed' => '5',
'allowed_html' => "<a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type='1 A I'> <li> <dl> <dt> <dd> <h2 id='jump-*'> <h3 id> <h4 id> <h5 id> <h6 id> <drupal-media data-entity-type data-entity-uuid data-view-mode>",
'expected_error_message' => 'The Embed media filter needs to be placed after the Restrict images to this site filter.',
],
];
}
/**
* Show visually hidden fields.
*/
protected function showHiddenFields() {
$script = <<<JS
var hidden_fields = document.querySelectorAll(".hidden");
[].forEach.call(hidden_fields, function(el) {
el.classList.remove("hidden");
});
JS;
$this->getSession()->executeScript($script);
}
}

View File

@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\FunctionalJavascript;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
use Drupal\Tests\media\Functional\MediaFunctionalTestTrait;
use Drupal\Tests\media\Traits\MediaTypeCreationTrait;
/**
* Base class for Media functional JavaScript tests.
*/
abstract class MediaJavascriptTestBase extends WebDriverTestBase {
use MediaFunctionalTestTrait;
use MediaTypeCreationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'system',
'node',
'field_ui',
'views_ui',
'media',
'media_test_source',
];
/**
* Waits and asserts that a given element is visible.
*
* @param string $selector
* The CSS selector.
* @param int $timeout
* (Optional) Timeout in milliseconds, defaults to 1000.
* @param string $message
* (Optional) Message to pass to assertJsCondition().
*/
protected function waitUntilVisible($selector, $timeout = 1000, $message = '') {
$condition = "jQuery('" . $selector . ":visible').length > 0";
$this->assertJsCondition($condition, $timeout, $message);
}
/**
* Asserts that a link to a new media item is displayed in the messages area.
*
* @return string
* The link URL.
*/
protected function assertLinkToCreatedMedia() {
$assert_session = $this->assertSession();
$selector = 'div[aria-label="Status message"] a';
// Get the canonical media entity URL from the creation message.
$link = $assert_session->elementExists('css', $selector);
$assert_session->elementAttributeExists('css', $selector, 'href');
return $link->getAttribute('href');
}
}

View File

@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\FunctionalJavascript;
/**
* Tests related to media reference fields.
*
* @group media
*/
class MediaReferenceFieldHelpTest extends MediaJavascriptTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $modules = [
'block',
'field_ui',
'media',
'media_library',
];
/**
* Tests our custom help texts when creating a field.
*
* @see media_form_field_ui_field_storage_add_form_alter()
*/
public function testFieldCreationHelpText(): void {
$this->drupalPlaceBlock('local_actions_block');
$assert_session = $this->assertSession();
$type = $this->drupalCreateContentType([
'type' => 'foo',
]);
$this->drupalGet("/admin/structure/types/manage/{$type->id()}/fields");
$this->clickLink('Create a new field');
$this->assertSession()->assertWaitOnAjaxRequest();
$field_groups = [
'File upload',
'Media',
];
$help_text = 'Use Media reference fields for most files, images, audio, videos, and remote media. Use File or Image reference fields when creating your own media types, or for legacy files and images created before installing the Media module.';
// Choose a boolean field, none of the description containers should be
// visible.
$this->clickLink('Boolean');
$assert_session->assertWaitOnAjaxRequest();
$assert_session->pageTextNotContains($help_text);
$assert_session->buttonExists('Change field type')->press();
$assert_session->assertWaitOnAjaxRequest();
// Select each of the Reference, File upload field groups and verify their
// descriptions are now visible and match the expected text.
foreach ($field_groups as $field_group) {
$this->clickLink($field_group);
$assert_session->assertWaitOnAjaxRequest();
$assert_session->pageTextContains($help_text);
$assert_session->buttonExists('Change field type')->press();
$assert_session->assertWaitOnAjaxRequest();
}
}
}

View File

@ -0,0 +1,122 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\FunctionalJavascript;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\file\Entity\File;
/**
* Tests the Audio and Video media sources.
*
* @group media
*/
class MediaSourceAudioVideoTest extends MediaSourceTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Check the Audio source functionality.
*/
public function testAudioTypeCreation(): void {
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
$source_id = 'audio_file';
$type_name = 'audio_type';
$field_name = 'field_media_' . $source_id;
$this->doTestCreateMediaType($type_name, $source_id);
// Check that the source field was created with the correct settings.
$storage = FieldStorageConfig::load("media.$field_name");
$this->assertInstanceOf(FieldStorageConfig::class, $storage);
$field = FieldConfig::load("media.$type_name.$field_name");
$this->assertInstanceOf(FieldConfig::class, $field);
$this->assertSame('mp3 wav aac', FieldConfig::load("media.$type_name.$field_name")->get('settings')['file_extensions']);
// Check that the display holds the correct formatter configuration.
$display = EntityViewDisplay::load("media.$type_name.default");
$this->assertInstanceOf(EntityViewDisplay::class, $display);
$formatter = $display->getComponent($field_name)['type'];
$this->assertSame('file_audio', $formatter);
// Create a media asset.
file_put_contents('public://file.mp3', str_repeat('t', 10));
$file = File::create([
'uri' => 'public://file.mp3',
'filename' => 'file.mp3',
]);
$file->save();
$this->drupalGet("media/add/$type_name");
$page->fillField('Name', 'Audio media asset');
$page->attachFileToField("files[{$field_name}_0]", \Drupal::service('file_system')->realpath('public://file.mp3'));
$result = $assert_session->waitForButton('Remove');
$this->assertNotEmpty($result);
$page->pressButton('Save');
// Verify that there is a creation message and that it contains a link to
// the media entity.
$assert_session->pageTextContains("$type_name Audio media asset has been created.");
$this->drupalGet($this->assertLinkToCreatedMedia());
// Verify that the <audio> tag is present on the media entity view.
$assert_session->elementExists('css', "audio > source[type='audio/mpeg']");
}
/**
* Check the Video source functionality.
*/
public function testVideoTypeCreation(): void {
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
$source_id = 'video_file';
$type_name = 'video_type';
$field_name = 'field_media_' . $source_id;
$this->doTestCreateMediaType($type_name, $source_id);
// Check that the source field was created with the correct settings.
$storage = FieldStorageConfig::load("media.$field_name");
$this->assertInstanceOf(FieldStorageConfig::class, $storage);
$field = FieldConfig::load("media.$type_name.$field_name");
$this->assertInstanceOf(FieldConfig::class, $field);
$this->assertSame('mp4', FieldConfig::load("media.$type_name.$field_name")->getSetting('file_extensions'));
// Check that the display holds the correct formatter configuration.
$display = EntityViewDisplay::load("media.$type_name.default");
$this->assertInstanceOf(EntityViewDisplay::class, $display);
$formatter = $display->getComponent($field_name)['type'];
$this->assertSame('file_video', $formatter);
// Create a media asset.
file_put_contents('public://file.mp4', str_repeat('t', 10));
$file = File::create([
'uri' => 'public://file.mp4',
'filename' => 'file.mp4',
]);
$file->save();
$this->drupalGet("media/add/$type_name");
$page->fillField('Name', 'Video media asset');
$page->attachFileToField("files[{$field_name}_0]", \Drupal::service('file_system')->realpath('public://file.mp4'));
$result = $assert_session->waitForButton('Remove');
$this->assertNotEmpty($result);
$page->pressButton('Save');
// Verify that there is a creation message and that it contains a link to
// the media entity.
$assert_session->pageTextContains("$type_name Video media asset has been created.");
$this->drupalGet($this->assertLinkToCreatedMedia());
// Verify that the <video> tag is present on the media entity view.
$assert_session->elementExists('css', "video > source[type='video/mp4']");
}
}

View File

@ -0,0 +1,118 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\FunctionalJavascript;
use Drupal\media\Entity\Media;
use Drupal\media\Plugin\media\Source\File;
/**
* Tests the file media source.
*
* @group media
*/
class MediaSourceFileTest extends MediaSourceTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests the file media source.
*/
public function testMediaFileSource(): void {
$media_type_id = 'test_media_file_type';
$source_field_id = 'field_media_file';
$provided_fields = [
File::METADATA_ATTRIBUTE_NAME,
File::METADATA_ATTRIBUTE_SIZE,
File::METADATA_ATTRIBUTE_MIME,
];
$session = $this->getSession();
$page = $session->getPage();
$assert_session = $this->assertSession();
$this->doTestCreateMediaType($media_type_id, 'file', $provided_fields);
// Create custom fields for the media type to store metadata attributes.
$fields = [
'field_string_file_size' => 'string',
'field_string_mime_type' => 'string',
];
$this->createMediaTypeFields($fields, $media_type_id);
// Hide the name field widget to test default name generation.
$this->hideMediaTypeFieldWidget('name', $media_type_id);
$this->drupalGet("admin/structure/media/manage/{$media_type_id}");
$page->selectFieldOption("field_map[" . File::METADATA_ATTRIBUTE_NAME . "]", 'name');
$page->selectFieldOption("field_map[" . File::METADATA_ATTRIBUTE_SIZE . "]", 'field_string_file_size');
$page->selectFieldOption("field_map[" . File::METADATA_ATTRIBUTE_MIME . "]", 'field_string_mime_type');
$page->pressButton('Save');
$test_filename = $this->randomMachineName() . '.txt';
$test_filepath = 'public://' . $test_filename;
file_put_contents($test_filepath, $this->randomMachineName());
// Create a media item.
$this->drupalGet("media/add/{$media_type_id}");
$page->attachFileToField("files[{$source_field_id}_0]", \Drupal::service('file_system')->realpath($test_filepath));
$result = $assert_session->waitForButton('Remove');
$this->assertNotEmpty($result);
$page->pressButton('Save');
$assert_session->addressEquals('admin/content/media');
// Get the media entity view URL from the creation message.
$this->drupalGet($this->assertLinkToCreatedMedia());
// Make sure a link to the file is displayed.
$assert_session->linkExists($test_filename);
// The thumbnail should not be displayed.
$assert_session->elementNotExists('css', 'img');
// Make sure checkbox changes the visibility of log message field.
$this->drupalGet("media/1/edit");
$page->uncheckField('revision');
$assert_session->elementAttributeContains('css', '.field--name-revision-log-message', 'style', 'display: none');
$page->checkField('revision');
$assert_session->elementAttributeNotContains('css', '.field--name-revision-log-message', 'style', 'display');
// Load the media and check that all the fields are properly populated.
$media = Media::load(1);
$this->assertSame($test_filename, $media->getName());
$this->assertSame('8', $media->get('field_string_file_size')->value);
$this->assertSame('text/plain', $media->get('field_string_mime_type')->value);
// Test the MIME type icon.
$icon_base = \Drupal::config('media.settings')->get('icon_base_uri');
\Drupal::service('file_system')->copy($icon_base . '/generic.png', $icon_base . '/text--plain.png');
$this->drupalGet("media/add/{$media_type_id}");
$page->attachFileToField("files[{$source_field_id}_0]", \Drupal::service('file_system')->realpath($test_filepath));
$result = $assert_session->waitForButton('Remove');
$this->assertNotEmpty($result);
$page->pressButton('Save');
$assert_session->elementAttributeContains('css', 'img', 'src', 'text--plain.png');
// Check if the mapped name is automatically updated.
$new_filename = $this->randomMachineName() . '.txt';
$new_filepath = 'public://' . $new_filename;
file_put_contents($new_filepath, $this->randomMachineName());
$this->drupalGet("media/1/edit");
$page->pressButton('Remove');
$result = $assert_session->waitForField("files[{$source_field_id}_0]");
$this->assertNotEmpty($result);
$page->attachFileToField("files[{$source_field_id}_0]", \Drupal::service('file_system')->realpath($new_filepath));
$result = $assert_session->waitForButton('Remove');
$this->assertNotEmpty($result);
$page->pressButton('Save');
/** @var \Drupal\media\MediaInterface $media */
$media = \Drupal::entityTypeManager()->getStorage('media')->loadUnchanged(1);
$this->assertEquals($new_filename, $media->getName());
$assert_session->statusMessageContains("$new_filename has been updated.", 'status');
}
}

View File

@ -0,0 +1,189 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\FunctionalJavascript;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
use Drupal\image\Entity\ImageStyle;
use Drupal\media\Entity\Media;
use Drupal\media\Entity\MediaType;
use Drupal\media\Plugin\media\Source\Image;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
/**
* Tests the image media source.
*
* @group media
*/
class MediaSourceImageTest extends MediaSourceTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests the image media source.
*/
public function testMediaImageSource(): void {
$media_type_id = 'test_media_image_type';
$source_field_id = 'field_media_image';
$provided_fields = [
Image::METADATA_ATTRIBUTE_WIDTH,
Image::METADATA_ATTRIBUTE_HEIGHT,
];
$session = $this->getSession();
$page = $session->getPage();
$assert_session = $this->assertSession();
$this->doTestCreateMediaType($media_type_id, 'image', $provided_fields);
// Create custom fields for the media type to store metadata attributes.
$fields = [
'field_string_width' => 'string',
'field_string_height' => 'string',
];
$this->createMediaTypeFields($fields, $media_type_id);
// Hide the name field widget to test default name generation.
$this->hideMediaTypeFieldWidget('name', $media_type_id);
$this->drupalGet("admin/structure/media/manage/{$media_type_id}");
$page->selectFieldOption("field_map[" . Image::METADATA_ATTRIBUTE_WIDTH . "]", 'field_string_width');
$page->selectFieldOption("field_map[" . Image::METADATA_ATTRIBUTE_HEIGHT . "]", 'field_string_height');
$page->pressButton('Save');
// Create a media item.
$this->drupalGet("media/add/{$media_type_id}");
$page->attachFileToField("files[{$source_field_id}_0]", $this->root . '/core/modules/media/tests/fixtures/example_1.jpeg');
$result = $assert_session->waitForButton('Remove');
$this->assertNotEmpty($result);
$page->fillField("{$source_field_id}[0][alt]", 'Image Alt Text 1');
$page->pressButton('Save');
$assert_session->addressEquals('admin/content/media');
// Get the media entity view URL from the creation message.
$this->drupalGet($this->assertLinkToCreatedMedia());
// Assert the image element is present inside the media element and that its
// src attribute uses the large image style, the label is visually hidden,
// and there is no link to the image file.
$label = $assert_session->elementExists('xpath', '//div[contains(@class, "visually-hidden") and text()="Image"]');
// The field is the parent div of the label.
$field = $label->getParent();
$image_element = $field->find('css', 'img');
/** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */
$file_url_generator = \Drupal::service('file_url_generator');
$expected_image_src = $file_url_generator->generate(\Drupal::token()->replace('public://styles/large/public/[date:custom:Y]-[date:custom:m]/example_1.jpeg'))->toString();
$this->assertStringContainsString($expected_image_src, $image_element->getAttribute('src'));
$assert_session->elementNotExists('css', 'a', $field);
// Ensure the image has the correct alt attribute.
$this->assertSame('Image Alt Text 1', $image_element->getAttribute('alt'));
// Load the media and check that all fields are properly populated.
$media = Media::load(1);
$this->assertSame('example_1.jpeg', $media->getName());
$this->assertSame('200', $media->get('field_string_width')->value);
$this->assertSame('89', $media->get('field_string_height')->value);
// Tests the warning when the default display's image style is missing.
$this->drupalLogin($this->drupalCreateUser([
'administer site configuration',
'access media overview',
'administer media',
'administer media types',
'administer media display',
'view media',
]));
$page = $this->getSession()->getPage();
$assert_session = $this->assertSession();
// If for some reason a site builder deletes the 'large' image style, do
// not add an image style to the new entity view display's image field.
// Instead, add a warning on the 'Status report' page.
ImageStyle::load('large')->delete();
$this->drupalGet('admin/structure/media/add');
$page->fillField('label', 'Ada Lovelace');
$this->assertNotEmpty($assert_session->waitForText('Machine name: ada_lovelace'));
$page->selectFieldOption('source', 'image');
// Wait for the form to complete with AJAX.
$this->assertNotEmpty($assert_session->waitForText('Field mapping'));
$page->pressButton('Save');
$this->assertViewDisplayConfigured('ada_lovelace');
// Create user without the 'administer media display' permission.
$this->drupalLogin($this->drupalCreateUser([
'administer site configuration',
'access media overview',
'administer media',
'administer media types',
'view media',
]));
// Test that hook_requirements adds warning about the lack of an image
// style.
$this->drupalGet('/admin/reports/status');
// The image style warning should not include an action link when the
// current user lacks the permission 'administer media display'.
$assert_session->pageTextContains('The default display for the Ada Lovelace media type is not currently using an image style on the Image field. Not using an image style can lead to much larger file downloads.');
$assert_session->linkNotExists('add an image style to the Image field');
$assert_session->linkByHrefNotExists('/admin/structure/media/manage/ada_lovelace/display');
// The image style warning should include an action link when the current
// user has the permission 'administer media display'.
Role::load(RoleInterface::AUTHENTICATED_ID)
->grantPermission('administer media display')
->save();
$this->drupalGet('/admin/reports/status');
$assert_session->pageTextContains('The default display for the Ada Lovelace media type is not currently using an image style on the Image field. Not using an image style can lead to much larger file downloads. If you would like to change this, add an image style to the Image field.');
$assert_session->linkExists('add an image style to the Image field');
$assert_session->linkByHrefExists('/admin/structure/media/manage/ada_lovelace/display');
// The image style warning should not include an action link when the
// Field UI module is uninstalled.
$this->container->get('module_installer')->uninstall(['field_ui']);
$this->drupalGet('/admin/reports/status');
$assert_session->pageTextContains('The default display for the Ada Lovelace media type is not currently using an image style on the Image field. Not using an image style can lead to much larger file downloads.');
$assert_session->linkNotExists('add an image style to the Image field');
$assert_session->linkByHrefNotExists('/admin/structure/media/manage/ada_lovelace/display');
}
/**
* Asserts the proper entity view display components for a media type.
*
* @param string $media_type_id
* The media type ID.
*
* @internal
*/
protected function assertViewDisplayConfigured(string $media_type_id): void {
$assert_session = $this->assertSession();
$type = MediaType::load($media_type_id);
$display = EntityViewDisplay::load('media.' . $media_type_id . '.' . EntityDisplayRepositoryInterface::DEFAULT_DISPLAY_MODE);
$this->assertInstanceOf(EntityViewDisplay::class, $display);
$source_field_definition = $type->getSource()->getSourceFieldDefinition($type);
$component = $display->getComponent($source_field_definition->getName());
$this->assertSame('visually_hidden', $component['label']);
if (ImageStyle::load('large')) {
$this->assertSame('large', $component['settings']['image_style']);
}
else {
$this->assertEmpty($component['settings']['image_style']);
}
$this->assertEmpty($component['settings']['image_link']);
// Since components that aren't explicitly hidden can show up on the
// display edit form, check that only the image field appears enabled on
// the display edit form.
$this->drupalGet('/admin/structure/media/manage/' . $media_type_id . '/display');
// Assert that only the source field is enabled.
$assert_session->elementExists('css', 'input[name="' . $source_field_definition->getName() . '_settings_edit"]');
$assert_session->elementsCount('css', 'input[name$="_settings_edit"]', 1);
}
}

View File

@ -0,0 +1,281 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\FunctionalJavascript;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Database\Database;
use Drupal\dblog\Controller\DbLogController;
use Drupal\media\Entity\Media;
use Drupal\media\Entity\MediaType;
use Drupal\media_test_oembed\Controller\ResourceController;
use Drupal\Tests\media\Traits\OEmbedTestTrait;
use Drupal\user\Entity\Role;
use Symfony\Component\DependencyInjection\ContainerInterface;
// cspell:ignore dailymotion Schipulcon
/**
* Tests the oembed:video media source.
*
* @group media
*/
class MediaSourceOEmbedVideoTest extends MediaSourceTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['media_test_oembed', 'dblog'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
use OEmbedTestTrait;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->lockHttpClientToFixtures();
}
/**
* {@inheritdoc}
*/
protected function initConfig(ContainerInterface $container): void {
parent::initConfig($container);
// Enable twig debugging to make testing template usage easy.
$parameters = $container->getParameter('twig.config');
$parameters['debug'] = TRUE;
$this->setContainerParameter('twig.config', $parameters);
}
/**
* Tests the oembed media source.
*/
public function testMediaOEmbedVideoSource(): void {
$media_type_id = 'test_media_oembed_type';
$provided_fields = [
'type',
'title',
'default_name',
'author_name',
'author_url',
'provider_name',
'provider_url',
'cache_age',
'thumbnail_uri',
'thumbnail_width',
'thumbnail_height',
'url',
'width',
'height',
'html',
];
$session = $this->getSession();
$page = $session->getPage();
$assert_session = $this->assertSession();
$this->doTestCreateMediaType($media_type_id, 'oembed:video', $provided_fields);
// Create custom fields for the media type to store metadata attributes.
$fields = [
'field_string_width' => 'string',
'field_string_height' => 'string',
'field_string_author_name' => 'string',
];
$this->createMediaTypeFields($fields, $media_type_id);
// Hide the name field widget to test default name generation.
$this->hideMediaTypeFieldWidget('name', $media_type_id);
$this->drupalGet("admin/structure/media/manage/$media_type_id");
// Only accept Vimeo videos.
$page->checkField("source_configuration[providers][Vimeo]");
$assert_session->selectExists('field_map[width]')->setValue('field_string_width');
$assert_session->selectExists('field_map[height]')->setValue('field_string_height');
$assert_session->selectExists('field_map[author_name]')->setValue('field_string_author_name');
$assert_session->buttonExists('Save')->press();
// Configure the iframe to be narrower than the actual video, so we can
// verify that the video scales correctly.
$display = \Drupal::service('entity_display.repository')->getViewDisplay('media', $media_type_id);
$this->assertFalse($display->isNew());
$component = $display->getComponent('field_media_oembed_video');
$this->assertIsArray($component);
$component['settings']['max_width'] = 240;
$display->setComponent('field_media_oembed_video', $component);
$this->assertSame(SAVED_UPDATED, $display->save());
$this->hijackProviderEndpoints();
$video_url = 'https://vimeo.com/7073899';
ResourceController::setResourceUrl($video_url, $this->getFixturesDirectory() . '/video_vimeo.json');
// Create a media item.
$this->drupalGet("media/add/$media_type_id");
$assert_session->fieldExists('Remote video URL')->setValue($video_url);
$assert_session->buttonExists('Save')->press();
$assert_session->addressEquals('admin/content/media');
// Get the media entity view URL from the creation message.
$this->drupalGet($this->assertLinkToCreatedMedia());
/** @var \Drupal\media\MediaInterface $media */
$media = Media::load(1);
// The thumbnail should have been downloaded.
$thumbnail = $media->getSource()->getMetadata($media, 'thumbnail_uri');
$this->assertFileExists($thumbnail);
// Ensure the iframe exists and has the expected CSS class, and that its src
// attribute contains a coherent URL with the query parameters we expect.
$iframe = $assert_session->elementExists('css', 'iframe.media-oembed-content');
$iframe_url = parse_url($iframe->getAttribute('src'));
$this->assertStringEndsWith('/media/oembed', $iframe_url['path']);
$this->assertNotEmpty($iframe_url['query']);
$query = [];
parse_str($iframe_url['query'], $query);
$this->assertSame($video_url, $query['url']);
$this->assertNotEmpty($query['hash']);
// Ensure that the outer iframe's width respects the formatter settings.
$this->assertSame('480', $iframe->getAttribute('width'));
// Check the inner iframe to make sure that CSS has been applied to scale it
// correctly, regardless of whatever its width attribute may be (the fixture
// hard-codes it to 480).
$inner_frame = 'frames[0].document.querySelector("iframe")';
$page->waitFor(10, fn () => $session->evaluateScript("$inner_frame !== null"));
$this->assertSame('480', $session->evaluateScript("$inner_frame.getAttribute('width')"));
$this->assertLessThanOrEqual(240, $session->evaluateScript("$inner_frame.clientWidth"));
// The oEmbed content iFrame should be visible.
$assert_session->elementExists('css', 'iframe.media-oembed-content');
// The thumbnail should not be displayed.
$assert_session->elementNotExists('css', 'img');
// Load the media and check that all fields are properly populated.
$media = Media::load(1);
$this->assertSame('Drupal Rap Video - Schipulcon09', $media->getName());
$this->assertSame('480', $media->field_string_width->value);
$this->assertSame('360', $media->field_string_height->value);
// Try to create a media asset from a disallowed provider.
$this->drupalGet("media/add/$media_type_id");
$assert_session->fieldExists('Remote video URL')->setValue('https://www.dailymotion.com/video/x2vzluh');
$page->pressButton('Save');
$assert_session->pageTextContains('The Dailymotion provider is not allowed.');
// Register a Dailymotion video as a second oEmbed resource. Note that its
// thumbnail URL does not have a file extension.
$media_type = MediaType::load($media_type_id);
$source_configuration = $media_type->getSource()->getConfiguration();
$source_configuration['providers'][] = 'Dailymotion';
$media_type->getSource()->setConfiguration($source_configuration);
$media_type->save();
$video_url = 'https://www.dailymotion.com/video/x2vzluh';
ResourceController::setResourceUrl($video_url, $this->getFixturesDirectory() . '/video_dailymotion.xml');
// Create a new media item using a Dailymotion video.
$this->drupalGet("media/add/$media_type_id");
$assert_session->fieldExists('Remote video URL')->setValue($video_url);
$assert_session->buttonExists('Save')->press();
/** @var \Drupal\media\MediaInterface $media */
$media = Media::load(2);
$thumbnail = $media->getSource()->getMetadata($media, 'thumbnail_uri');
$this->assertFileExists($thumbnail);
// Although the resource's thumbnail URL doesn't have a file extension, we
// should have deduced the correct one.
$this->assertStringEndsWith('.png', $thumbnail);
// Test ResourceException logging.
$video_url = 'https://vimeo.com/1111';
ResourceController::setResourceUrl($video_url, $this->getFixturesDirectory() . '/video_vimeo.json');
$this->drupalGet("media/add/$media_type_id");
$assert_session->fieldExists('Remote video URL')->setValue($video_url);
$assert_session->buttonExists('Save')->press();
$assert_session->addressEquals('admin/content/media');
ResourceController::setResource404($video_url);
$this->drupalGet($this->assertLinkToCreatedMedia());
$row = Database::getConnection()->select('watchdog')
->fields('watchdog', ['message', 'variables'])
->orderBy('wid', 'DESC')
->range(0, 1)
->execute()
->fetchObject();
$message = (string) DbLogController::create($this->container)->formatMessage($row);
$this->assertStringContainsString('resulted in a `404 Not Found` response', $message);
// Test anonymous access to media via iframe.
$this->drupalLogout();
// Without a hash should be denied.
$no_hash_query = array_diff_key($query, ['hash' => '']);
$this->drupalGet('media/oembed', ['query' => $no_hash_query]);
$assert_session->pageTextNotContains('Vimeo works!');
$assert_session->pageTextContains('This resource is not available');
// A correct query should be allowed because the anonymous role has the
// 'view media' permission.
$this->drupalGet('media/oembed', ['query' => $query]);
$assert_session->pageTextContains('Vimeo works!');
// Remove the 'view media' permission to test that this restricts access.
$role = Role::load(AccountInterface::ANONYMOUS_ROLE);
$role->revokePermission('view media');
$role->save();
$this->drupalGet('media/oembed', ['query' => $query]);
$assert_session->pageTextNotContains('Vimeo works!');
$assert_session->pageTextContains('Access denied');
}
/**
* Tests that a security warning appears if iFrame domain is not set.
*/
public function testOEmbedSecurityWarning(): void {
$media_type_id = 'test_media_oembed_type';
$source_id = 'oembed:video';
$session = $this->getSession();
$page = $session->getPage();
$assert_session = $this->assertSession();
$this->drupalGet('admin/structure/media/add');
$page->fillField('label', $media_type_id);
$this->getSession()
->wait(5000, "jQuery('.machine-name-value').text() === '{$media_type_id}'");
// Make sure the source is available.
$assert_session->fieldExists('Media source');
$assert_session->optionExists('Media source', $source_id);
$page->selectFieldOption('Media source', $source_id);
$result = $assert_session->waitForElementVisible('css', 'fieldset[data-drupal-selector="edit-source-configuration"]');
$this->assertNotEmpty($result);
$assert_session->pageTextContains('It is potentially insecure to display oEmbed content in a frame');
$this->config('media.settings')->set('iframe_domain', 'http://example.com')->save();
$this->drupalGet('admin/structure/media/add');
$page->fillField('label', $media_type_id);
$this->getSession()
->wait(5000, "jQuery('.machine-name-value').text() === '{$media_type_id}'");
// Make sure the source is available.
$assert_session->fieldExists('Media source');
$assert_session->optionExists('Media source', $source_id);
$page->selectFieldOption('Media source', $source_id);
$result = $assert_session->waitForElementVisible('css', 'fieldset[data-drupal-selector="edit-source-configuration"]');
$this->assertNotEmpty($result);
$assert_session->pageTextNotContains('It is potentially insecure to display oEmbed content in a frame');
}
}

View File

@ -0,0 +1,177 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\FunctionalJavascript;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\media\Entity\MediaType;
/**
* Base class for media source tests.
*/
abstract class MediaSourceTestBase extends MediaJavascriptTestBase {
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Let's set the canonical flag in the base class of the source tests,
// because every source test has to check the output on the view page.
\Drupal::configFactory()
->getEditable('media.settings')
->set('standalone_url', TRUE)
->save(TRUE);
$this->container->get('router.builder')->rebuild();
}
/**
* Creates storage and field instance, attached to a given media type.
*
* @param string $field_name
* The field name.
* @param string $field_type
* The field type.
* @param string $media_type_id
* The media type config entity ID.
*/
protected function createMediaTypeField($field_name, $field_type, $media_type_id) {
$storage = FieldStorageConfig::create([
'field_name' => $field_name,
'entity_type' => 'media',
'type' => $field_type,
]);
$storage->save();
FieldConfig::create([
'field_storage' => $storage,
'bundle' => $media_type_id,
])->save();
// Make the field widget visible in the form display.
$component = \Drupal::service('plugin.manager.field.widget')
->prepareConfiguration($field_type, []);
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
$display_repository = \Drupal::service('entity_display.repository');
$entity_form_display = $display_repository->getFormDisplay('media', $media_type_id, 'default');
$entity_form_display->setComponent($field_name, $component)
->save();
// Use the default formatter and settings.
$component = \Drupal::service('plugin.manager.field.formatter')
->prepareConfiguration($field_type, []);
$entity_display = $display_repository->getViewDisplay('media', $media_type_id);
$entity_display->setComponent($field_name, $component)
->save();
}
/**
* Create a set of fields in a media type.
*
* @param array $fields
* An associative array where keys are field names and values field types.
* @param string $media_type_id
* The media type config entity ID.
*/
protected function createMediaTypeFields(array $fields, $media_type_id) {
foreach ($fields as $field_name => $field_type) {
$this->createMediaTypeField($field_name, $field_type, $media_type_id);
}
}
/**
* Hides a widget in the default form display config.
*
* @param string $field_name
* The field name.
* @param string $media_type_id
* The media type config entity ID.
*/
protected function hideMediaTypeFieldWidget($field_name, $media_type_id) {
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
$display_repository = \Drupal::service('entity_display.repository');
/** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $entity_form_display */
$entity_form_display = $display_repository->getFormDisplay('media', $media_type_id, 'default');
if ($entity_form_display->getComponent($field_name)) {
$entity_form_display->removeComponent($field_name)->save();
}
}
/**
* Tests generic media type creation.
*
* @param string $media_type_id
* The media type config entity ID.
* @param string $source_id
* The media source ID.
* @param array $provided_fields
* (optional) An array of field machine names this type provides.
* @param string $source_label_visibility
* (optional) The visibility that the source field label is expected to
* have. Defaults to 'visually_hidden'.
*
* @return \Drupal\media\MediaTypeInterface
* The created media type.
*/
public function doTestCreateMediaType($media_type_id, $source_id, array $provided_fields = [], $source_label_visibility = 'visually_hidden') {
$session = $this->getSession();
$page = $session->getPage();
$assert_session = $this->assertSession();
$this->drupalGet('admin/structure/media/add');
$page->fillField('label', $media_type_id);
$this->getSession()
->wait(5000, "jQuery('.machine-name-value').text() === '{$media_type_id}'");
// Make sure the source is available.
$assert_session->fieldExists('Media source');
$assert_session->optionExists('Media source', $source_id);
$page->selectFieldOption('Media source', $source_id);
$result = $assert_session->waitForElementVisible('css', 'fieldset[data-drupal-selector="edit-source-configuration"]');
$this->assertNotEmpty($result);
// Make sure the provided fields are visible on the form.
foreach ($provided_fields as $provided_field) {
$result = $assert_session->waitForElementVisible('css', 'select[name="field_map[' . $provided_field . ']"]');
$this->assertNotEmpty($result);
}
// Save the form to create the type.
$page->pressButton('Save');
$assert_session->pageTextContains('The media type ' . $media_type_id . ' has been added.');
$this->drupalGet('admin/structure/media');
$assert_session->pageTextContains($media_type_id);
$media_type = MediaType::load($media_type_id);
// Assert that the default display of the media type only shows the source
// field.
$this->drupalGet("/admin/structure/media/manage/$media_type_id/display");
// There should be only one field with editable settings, and it should be
// the source field.
$assert_session->elementsCount('css', 'input[name$="_settings_edit"]', 1);
$source_field_name = $media_type->getSource()
->getSourceFieldDefinition($media_type)
->getName();
$assert_session->buttonExists("{$source_field_name}_settings_edit");
// Ensure the source field label is configured as expected.
$assert_session->fieldValueEquals("fields[$source_field_name][label]", $source_label_visibility);
// Bundle definitions are statically cached in the context of the test, we
// need to make sure we have updated information before proceeding with the
// actions on the UI.
\Drupal::service('entity_type.bundle.info')->clearCachedBundles();
return $media_type;
}
}

View File

@ -0,0 +1,572 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\FunctionalJavascript;
use Drupal\Core\Database\Database;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\media_test_oembed\Controller\ResourceController;
use Drupal\node\Entity\Node;
use Drupal\Tests\media\Traits\OEmbedTestTrait;
// cspell:ignore Drupalin Hustlin Schipulcon
/**
* Basic tests for Media configuration in the standard profile.
*
* @group media
*/
class MediaStandardProfileTest extends MediaJavascriptTestBase {
use OEmbedTestTrait;
/**
* {@inheritdoc}
*/
protected $profile = 'standard';
/**
* {@inheritdoc}
*/
protected static $modules = ['media_test_oembed'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->lockHttpClientToFixtures();
$this->hijackProviderEndpoints();
}
/**
* Tests all media sources in one method.
*
* This prevents installing the standard profile for every test case and
* increases the performance of this test.
*/
public function testMediaSources(): void {
// This test currently frequently causes the SQLite database to lock, so
// skip the test on SQLite until the issue can be resolved.
// @todo https://www.drupal.org/project/drupal/issues/3273626
if (Database::getConnection()->driver() === 'sqlite') {
$this->markTestSkipped('Test frequently causes a locked database on SQLite');
}
$storage = FieldStorageConfig::create([
'entity_type' => 'node',
'field_name' => 'field_related_media',
'type' => 'entity_reference',
'settings' => [
'target_type' => 'media',
],
]);
$storage->save();
FieldConfig::create([
'field_storage' => $storage,
'entity_type' => 'node',
'bundle' => 'article',
'label' => 'Related media',
'settings' => [
'handler_settings' => [
'target_bundles' => [
'audio' => 'audio',
'document' => 'document',
'image' => 'image',
'remote_video' => 'remote_video',
'video' => 'video',
],
],
],
])->save();
$display = EntityViewDisplay::load('node.article.default');
$display->setComponent('field_related_media', [
'type' => 'entity_reference_entity_view',
'settings' => [
'view_mode' => 'full',
],
])->save();
$this->audioTest();
$this->documentTest();
$this->imageTest();
$this->remoteVideoTest();
$this->videoTest();
}
/**
* Tests the standard profile configuration for media type 'audio'.
*/
protected function audioTest(): void {
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
$source_field_id = 'field_media_audio_file';
// Create 2 test files.
$test_filename = $this->randomMachineName() . '.mp3';
$test_filepath = 'public://' . $test_filename;
$test_filename_updated = $this->randomMachineName() . '.mp3';
$test_filepath_updated = 'public://' . $test_filename_updated;
file_put_contents($test_filepath, str_repeat('t', 10));
file_put_contents($test_filepath_updated, str_repeat('u', 10));
// Check if the name field is properly hidden on the media form.
$this->drupalGet('media/add/audio');
$assert_session->fieldNotExists('name');
// Check if the source field is available.
$assert_session->fieldExists("files[{$source_field_id}_0]");
// Create a media item.
$page->attachFileToField("files[{$source_field_id}_0]", \Drupal::service('file_system')->realpath($test_filepath));
$result = $assert_session->waitForButton('Remove');
$this->assertNotEmpty($result);
$page->pressButton('Save');
$audio_media_id = $this->container
->get('entity_type.manager')
->getStorage('media')
->getQuery()
->accessCheck(FALSE)
->sort('mid', 'DESC')
->execute();
$audio_media_id = reset($audio_media_id);
// Reference the created media using an entity_reference field and make sure
// the output is what we expect.
$node = Node::create([
'title' => 'Host node',
'type' => 'article',
'field_related_media' => [
'target_id' => $audio_media_id,
],
]);
$node->save();
$this->drupalGet('/node/' . $node->id());
// Check if the default media name is generated as expected.
$media = \Drupal::entityTypeManager()->getStorage('media')->loadUnchanged($audio_media_id);
$this->assertSame($test_filename, $media->label());
// Here we expect to see only the linked filename. Assert only one element
// in the content region.
$assert_session->elementsCount('css', 'div.media--type-audio > *', 1);
// Assert the audio file is present inside the media element and that its
// src attribute matches the audio file.
$audio_element = $assert_session->elementExists('css', 'div.media--type-audio .field--name-field-media-audio-file audio > source');
/** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */
$file_url_generator = \Drupal::service('file_url_generator');
$expected_audio_src = $file_url_generator->generate(\Drupal::token()->replace('public://[date:custom:Y]-[date:custom:m]/' . $test_filename))->toString();
$this->assertSame($expected_audio_src, $audio_element->getAttribute('src'));
// Assert the media name is updated through the field mapping when changing
// the source field.
$this->drupalGet('media/' . $audio_media_id . '/edit');
$page->pressButton('Remove');
$result = $assert_session->waitForField("files[{$source_field_id}_0]");
$this->assertNotEmpty($result);
$page->attachFileToField("files[{$source_field_id}_0]", \Drupal::service('file_system')->realpath($test_filepath_updated));
$result = $assert_session->waitForButton('Remove');
$this->assertNotEmpty($result);
$page->pressButton('Save');
$this->drupalGet('/node/' . $node->id());
// Check if the default media name is updated as expected.
$media = \Drupal::entityTypeManager()->getStorage('media')->loadUnchanged($audio_media_id);
$this->assertSame($test_filename_updated, $media->label());
// Again we expect to see only the linked filename. Assert only one element
// in the content region.
$assert_session->elementsCount('css', 'div.media--type-audio > *', 1);
// Assert the audio file is present inside the media element and that its
// src attribute matches the updated audio file.
$audio_element = $assert_session->elementExists('css', 'div.media--type-audio .field--name-field-media-audio-file audio > source');
$expected_audio_src = $file_url_generator->generate(\Drupal::token()->replace('public://[date:custom:Y]-[date:custom:m]/' . $test_filename_updated))->toString();
$this->assertSame($expected_audio_src, $audio_element->getAttribute('src'));
}
/**
* Tests the standard profile configuration for media type 'image'.
*/
protected function imageTest(): void {
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
$source_field_id = 'field_media_image';
// Check if the name field is properly hidden on the media form.
$this->drupalGet('media/add/image');
$assert_session->fieldNotExists('name');
// Check if the source field is available.
$assert_session->fieldExists("files[{$source_field_id}_0]");
// Create a media item.
$image_media_name = 'example_1.jpeg';
$page->attachFileToField("files[{$source_field_id}_0]", $this->root . '/core/modules/media/tests/fixtures/' . $image_media_name);
$result = $assert_session->waitForButton('Remove');
$this->assertNotEmpty($result);
$page->fillField("{$source_field_id}[0][alt]", 'Image Alt Text 1');
$page->pressButton('Save');
$image_media_id = $this->container
->get('entity_type.manager')
->getStorage('media')
->getQuery()
->accessCheck(FALSE)
->sort('mid', 'DESC')
->execute();
$image_media_id = reset($image_media_id);
// Reference the created media using an entity_reference field and make sure
// the output is what we expect.
$node = Node::create([
'title' => 'Host node',
'type' => 'article',
'field_related_media' => [
'target_id' => $image_media_id,
],
]);
$node->save();
$this->drupalGet('/node/' . $node->id());
// Check if the default media name is generated as expected.
$media = \Drupal::entityTypeManager()->getStorage('media')->loadUnchanged($image_media_id);
$this->assertSame($image_media_name, $media->label());
// Here we expect to see only the image, nothing else. Assert only one
// element in the content region.
$assert_session->elementsCount('css', 'div.media--type-image > *', 1);
// Assert the image element is present inside the media element and that its
// src attribute uses the large image style, the label is visually hidden,
// and there is no link to the image file.
$image_element = $assert_session->elementExists('css', 'div.media--type-image img');
/** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */
$file_url_generator = \Drupal::service('file_url_generator');
$expected_image_src = $file_url_generator->generate(\Drupal::token()->replace('public://styles/large/public/[date:custom:Y]-[date:custom:m]/' . $image_media_name))->toString();
$this->assertStringContainsString($expected_image_src, $image_element->getAttribute('src'));
$assert_session->elementExists('css', '.field--name-field-media-image .field__label.visually-hidden');
$assert_session->elementNotExists('css', '.field--name-field-media-image a');
// Assert the media name is updated through the field mapping when changing
// the source field.
$this->drupalGet('media/' . $image_media_id . '/edit');
$page->pressButton('Remove');
$result = $assert_session->waitForField("files[{$source_field_id}_0]");
$this->assertNotEmpty($result);
$image_media_name_updated = 'example_2.jpeg';
$page->attachFileToField("files[{$source_field_id}_0]", $this->root . '/core/modules/media/tests/fixtures/' . $image_media_name_updated);
$result = $assert_session->waitForButton('Remove');
$this->assertNotEmpty($result);
$page->fillField("{$source_field_id}[0][alt]", 'Image Alt Text 2');
$page->pressButton('Save');
$this->drupalGet('/node/' . $node->id());
// Check if the default media name is updated as expected.
$media = \Drupal::entityTypeManager()->getStorage('media')->loadUnchanged($image_media_id);
$this->assertSame($image_media_name_updated, $media->label());
// Again we expect to see only the image, nothing else. Assert only one
// element in the content region.
$assert_session->elementsCount('css', 'div.media--type-image > *', 1);
// Assert the image element is present inside the media element and that its
// src attribute uses the large image style, the label is visually hidden,
// and there is no link to the image file.
$image_element = $assert_session->elementExists('css', 'div.media--type-image img');
$expected_image_src = $file_url_generator->generate(\Drupal::token()->replace('public://styles/large/public/[date:custom:Y]-[date:custom:m]/' . $image_media_name_updated))->toString();
$this->assertStringContainsString($expected_image_src, $image_element->getAttribute('src'));
$assert_session->elementExists('css', '.field--name-field-media-image .field__label.visually-hidden');
$assert_session->elementNotExists('css', '.field--name-field-media-image a');
}
/**
* Tests the standard profile configuration for media type 'document'.
*/
protected function documentTest(): void {
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
$source_field_id = 'field_media_document';
// Create 2 test files.
$test_filename = $this->randomMachineName() . '.txt';
$test_filepath = 'public://' . $test_filename;
$test_filename_updated = $this->randomMachineName() . '.txt';
$test_filepath_updated = 'public://' . $test_filename_updated;
file_put_contents($test_filepath, $this->randomMachineName());
file_put_contents($test_filepath_updated, $this->randomMachineName());
// Check if the name field is properly hidden on the media form.
$this->drupalGet('media/add/document');
$assert_session->fieldNotExists('name');
// Check if the source field is available.
$assert_session->fieldExists("files[{$source_field_id}_0]");
// Create a media item.
$page->attachFileToField("files[{$source_field_id}_0]", \Drupal::service('file_system')->realpath($test_filepath));
$result = $assert_session->waitForButton('Remove');
$this->assertNotEmpty($result);
$page->pressButton('Save');
$file_media_id = $this->container
->get('entity_type.manager')
->getStorage('media')
->getQuery()
->accessCheck(FALSE)
->sort('mid', 'DESC')
->execute();
$file_media_id = reset($file_media_id);
// Reference the created media using an entity_reference field and make sure
// the output is what we expect.
$node = Node::create([
'title' => 'Host node',
'type' => 'article',
'field_related_media' => [
'target_id' => $file_media_id,
],
]);
$node->save();
$this->drupalGet('/node/' . $node->id());
// Check if the default media name is generated as expected.
$media = \Drupal::entityTypeManager()->getStorage('media')->loadUnchanged($file_media_id);
$this->assertSame($test_filename, $media->label());
// Here we expect to see only the linked filename. Assert only one element
// in the content region.
$assert_session->elementsCount('css', 'div.media--type-document > *', 1);
// Assert the file link is present in the media element and its text matches
// the filename.
$link_element = $assert_session->elementExists('css', 'div.media--type-document .field--name-field-media-document a');
$this->assertSame($test_filename, $link_element->getText());
// Assert the media name is updated through the field mapping when changing
// the source field.
$this->drupalGet('media/' . $file_media_id . '/edit');
$page->pressButton('Remove');
$result = $assert_session->waitForField("files[{$source_field_id}_0]");
$this->assertNotEmpty($result);
$page->attachFileToField("files[{$source_field_id}_0]", \Drupal::service('file_system')->realpath($test_filepath_updated));
$result = $assert_session->waitForButton('Remove');
$this->assertNotEmpty($result);
$page->pressButton('Save');
$this->drupalGet('/node/' . $node->id());
// Check if the default media name is updated as expected.
$media = \Drupal::entityTypeManager()->getStorage('media')->loadUnchanged($file_media_id);
$this->assertSame($test_filename_updated, $media->label());
// Again we expect to see only the linked filename. Assert only one element
// in the content region.
$assert_session->elementsCount('css', 'div.media--type-document > *', 1);
// Assert the file link is present in the media element and its text matches
// the updated filename.
$link_element = $assert_session->elementExists('css', 'div.media--type-document .field--name-field-media-document a');
$this->assertSame($test_filename_updated, $link_element->getText());
}
/**
* Tests the standard profile configuration for media type 'remote_video'.
*/
protected function remoteVideoTest(): void {
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
$source_field_id = 'field_media_oembed_video';
// Set video fixtures.
$video_title = 'Drupal Rap Video - Schipulcon09';
$video_url = 'https://vimeo.com/7073899';
ResourceController::setResourceUrl($video_url, $this->getFixturesDirectory() . '/video_vimeo.json');
$video_title_updated = "Everyday I'm Drupalin' Drupal Rap (Rick Ross - Hustlin)";
$video_url_updated = 'https://www.youtube.com/watch?v=PWjcqE3QKBg';
ResourceController::setResourceUrl($video_url_updated, $this->getFixturesDirectory() . '/video_youtube.json');
// Check if the name field is properly hidden on the media form.
$this->drupalGet('media/add/remote_video');
$assert_session->fieldNotExists('name');
// Check if the source field is available.
$assert_session->fieldExists("{$source_field_id}[0][value]");
// Create a media item.
$page->fillField("{$source_field_id}[0][value]", $video_url);
$page->pressButton('Save');
$remote_video_media_id = $this->container
->get('entity_type.manager')
->getStorage('media')
->getQuery()
->accessCheck(FALSE)
->sort('mid', 'DESC')
->execute();
$remote_video_media_id = reset($remote_video_media_id);
// Reference the created media using an entity_reference field and make sure
// the output is what we expect.
$node = Node::create([
'title' => 'Host node',
'type' => 'article',
'field_related_media' => [
'target_id' => $remote_video_media_id,
],
]);
$node->save();
$this->drupalGet('/node/' . $node->id());
// Check if the default media name is generated as expected.
$media = \Drupal::entityTypeManager()->getStorage('media')->loadUnchanged($remote_video_media_id);
$this->assertSame($video_title, $media->label());
// Here we expect to see only the video iframe. Assert only one element in
// the content region.
$assert_session->elementsCount('css', 'div.media--type-remote-video > *', 1);
// Assert the iframe is present in the media element and its src attribute
// matches the URL and query parameters.
$iframe_url = $assert_session->elementExists('css', 'div.media--type-remote-video .field--name-field-media-oembed-video iframe')->getAttribute('src');
$iframe_url = parse_url($iframe_url);
$this->assertStringEndsWith('/media/oembed', $iframe_url['path']);
$this->assertNotEmpty($iframe_url['query']);
$query = [];
parse_str($iframe_url['query'], $query);
$this->assertSame($video_url, $query['url']);
$this->assertNotEmpty($query['hash']);
// Assert the media name is updated through the field mapping when changing
// the source field.
$this->drupalGet('media/' . $remote_video_media_id . '/edit');
$page->fillField("{$source_field_id}[0][value]", $video_url_updated);
$page->pressButton('Save');
$this->drupalGet('/node/' . $node->id());
// Check if the default media name is updated as expected.
$media = \Drupal::entityTypeManager()->getStorage('media')->loadUnchanged($remote_video_media_id);
$this->assertSame($video_title_updated, $media->label());
// Again we expect to see only the video iframe. Assert only one element in
// the content region.
$assert_session->elementsCount('css', 'div.media--type-remote-video > *', 1);
// Assert the iframe is present in the media element and its src attribute
// matches the updated URL and query parameters.
$iframe_url = $assert_session->elementExists('css', 'div.media--type-remote-video .field--name-field-media-oembed-video iframe')->getAttribute('src');
$iframe_url = parse_url($iframe_url);
$this->assertStringEndsWith('/media/oembed', $iframe_url['path']);
$this->assertNotEmpty($iframe_url['query']);
$query = [];
parse_str($iframe_url['query'], $query);
$this->assertSame($video_url_updated, $query['url']);
$this->assertNotEmpty($query['hash']);
}
/**
* Tests the standard profile configuration for media type 'video'.
*/
protected function videoTest(): void {
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
$source_field_id = 'field_media_video_file';
// Create 2 test files.
$test_filename = $this->randomMachineName() . '.mp4';
$test_filepath = 'public://' . $test_filename;
$test_filename_updated = $this->randomMachineName() . '.mp4';
$test_filepath_updated = 'public://' . $test_filename_updated;
file_put_contents($test_filepath, str_repeat('t', 10));
file_put_contents($test_filepath_updated, str_repeat('u', 10));
// Check if the name field is properly hidden on the media form.
$this->drupalGet('media/add/video');
$assert_session->fieldNotExists('name');
// Check if the source field is available.
$assert_session->fieldExists("files[{$source_field_id}_0]");
// Create a media item.
$page->attachFileToField("files[{$source_field_id}_0]", \Drupal::service('file_system')->realpath($test_filepath));
$result = $assert_session->waitForButton('Remove');
$this->assertNotEmpty($result);
$page->pressButton('Save');
$video_media_id = $this->container
->get('entity_type.manager')
->getStorage('media')
->getQuery()
->accessCheck(FALSE)
->sort('mid', 'DESC')
->execute();
$video_media_id = reset($video_media_id);
// Reference the created media using an entity_reference field and make sure
// the output is what we expect.
$node = Node::create([
'title' => 'Host node',
'type' => 'article',
'field_related_media' => [
'target_id' => $video_media_id,
],
]);
$node->save();
$this->drupalGet('/node/' . $node->id());
// Check if the default media name is generated as expected.
$media = \Drupal::entityTypeManager()->getStorage('media')->loadUnchanged($video_media_id);
$this->assertSame($test_filename, $media->label());
// Here we expect to see only the linked filename. Assert only one element
// in the content region.
$assert_session->elementsCount('css', 'div.media--type-video > *', 1);
// Assert the video element is present inside the media element and that its
// src attribute matches the video file.
$video_element = $assert_session->elementExists('css', 'div.media--type-video .field--name-field-media-video-file video > source');
/** @var \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator */
$file_url_generator = \Drupal::service('file_url_generator');
$expected_video_src = $file_url_generator->generate(\Drupal::token()->replace('public://[date:custom:Y]-[date:custom:m]/' . $test_filename))->toString();
$this->assertSame($expected_video_src, $video_element->getAttribute('src'));
// Assert the media name is updated through the field mapping when changing
// the source field.
$this->drupalGet('media/' . $video_media_id . '/edit');
$page->pressButton('Remove');
$result = $assert_session->waitForField("files[{$source_field_id}_0]");
$this->assertNotEmpty($result);
$page->attachFileToField("files[{$source_field_id}_0]", \Drupal::service('file_system')->realpath($test_filepath_updated));
$result = $assert_session->waitForButton('Remove');
$this->assertNotEmpty($result);
$page->pressButton('Save');
$this->drupalGet('/node/' . $node->id());
// Check if the default media name is updated as expected.
$media = \Drupal::entityTypeManager()->getStorage('media')->loadUnchanged($video_media_id);
$this->assertSame($test_filename_updated, $media->label());
// Again we expect to see only the linked filename. Assert only one element
// in the content region.
$assert_session->elementsCount('css', 'div.media--type-video > *', 1);
// Assert the video element is present inside the media element and that its
// src attribute matches the updated video file.
$video_element = $assert_session->elementExists('css', 'div.media--type-video .field--name-field-media-video-file video > source');
$expected_video_src = $file_url_generator->generate(\Drupal::token()->replace('public://[date:custom:Y]-[date:custom:m]/' . $test_filename_updated))->toString();
$this->assertSame($expected_video_src, $video_element->getAttribute('src'));
}
}

View File

@ -0,0 +1,199 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\FunctionalJavascript;
use Drupal\Component\Utility\Html;
// cspell:ignore pastafazoul
/**
* Tests the media type creation.
*
* @group media
*/
class MediaTypeCreationTest extends MediaJavascriptTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests the source field behavior on the add media type form.
*/
public function testSourceChangeOnMediaTypeCreationForm(): void {
$session = $this->getSession();
$page = $session->getPage();
$assert_session = $this->assertSession();
$label = 'Type with Default Field';
$mediaTypeMachineName = str_replace(' ', '_', strtolower($label));
$this->drupalGet('admin/structure/media/add');
// Fill in a label to the media type.
$page->fillField('label', $label);
$this->assertNotEmpty(
$assert_session->waitForElementVisible('css', '.machine-name-value')
);
// Select the media source used by our media type.
$assert_session->selectExists('Media source')->selectOption('test_different_displays');
$this->assertNotEmpty(
$assert_session->waitForElementVisible('css', 'fieldset[data-drupal-selector="edit-source-configuration"]')
);
// Change the media source.
$assert_session->selectExists('Media source')->selectOption('test');
$this->assertNotEmpty(
$assert_session->waitForElement('css', 'fieldset[data-drupal-selector="edit-source-configuration"] .fieldset-wrapper .placeholder:contains("Text (plain)")')
);
$page->pressButton('Save');
// Check that source can not be changed anymore.
$this->drupalGet("admin/structure/media/manage/{$mediaTypeMachineName}");
$assert_session->pageTextContains('The media source cannot be changed after the media type is created');
$assert_session->fieldDisabled('Media source');
}
/**
* Tests the media type creation form.
*/
public function testMediaTypeCreationFormWithDefaultField(): void {
$session = $this->getSession();
$page = $session->getPage();
$assert_session = $this->assertSession();
$label = 'Type with Default Field';
$mediaTypeMachineName = str_replace(' ', '_', strtolower($label));
$this->drupalGet('admin/structure/media/add');
// Select the media source used by our media type. Do this before setting
// the label or machine name in order to guard against the regression in
// https://www.drupal.org/project/drupal/issues/2557299.
$assert_session->fieldExists('Media source');
$assert_session->optionExists('Media source', 'test');
$page->selectFieldOption('Media source', 'test');
$result = $assert_session->waitForElementVisible('css', 'fieldset[data-drupal-selector="edit-source-configuration"]');
$this->assertNotEmpty($result);
// Fill in a label to the media type.
$page->fillField('label', $label);
// Wait for machine name generation. Default: waitUntilVisible(), does not
// work properly.
$session->wait(5000, "jQuery('.machine-name-value').text() === '{$mediaTypeMachineName}'");
$page->pressButton('Save');
// Check whether the source field was correctly created.
$this->drupalGet("admin/structure/media/manage/{$mediaTypeMachineName}/fields");
// Check 2nd column of first data row, to be machine name for field name.
$assert_session->elementContains('xpath', '(//table[@id="field-overview"]//tr)[2]//td[2]', 'field_media_test');
// Check 3rd column of first data row, to be correct field type.
$assert_session->elementTextContains('xpath', '(//table[@id="field-overview"]//tr)[2]//td[3]', 'Text (plain)');
// Check that the source field is correctly assigned to media type.
$this->drupalGet("admin/structure/media/manage/{$mediaTypeMachineName}");
$assert_session->pageTextContains('Test source field is used to store the essential information about the media item.');
// Check that the plugin cannot be changed after it is set on type creation.
$assert_session->fieldDisabled('Media source');
$assert_session->pageTextContains('The media source cannot be changed after the media type is created.');
// Check that the field map options are sorted alphabetically.
// Source field should not be included.
$options = $this->xpath('//select[@name="field_map[attribute_1]"]/option');
$this->assertGreaterThanOrEqual(2, count($options));
$this->assertSame('- Skip field -', $options[0]->getText());
$this->assertSame('Name', $options[1]->getText());
// It should not be possible to map the source field.
$assert_session->optionNotExists('field_map[attribute_1]', 'Test source');
// Open up the media add form and verify that the source field is right
// after the name, and before the vertical tabs.
$this->drupalGet("/media/add/$mediaTypeMachineName");
// Get the form element, and its HTML representation.
$form_selector = '#media-' . Html::cleanCssIdentifier($mediaTypeMachineName) . '-add-form';
$form = $assert_session->elementExists('css', $form_selector);
$form_html = $form->getOuterHtml();
// The name field should come before the source field, which should itself
// come before the vertical tabs.
$name_field = $assert_session->fieldExists('Name', $form)->getOuterHtml();
$test_source_field = $assert_session->fieldExists('Test source', $form)->getOuterHtml();
$vertical_tabs = $assert_session->elementExists('css', '.vertical-tabs', $form)->getOuterHtml();
$date_field = $assert_session->fieldExists('Date', $form)->getOuterHtml();
$published_checkbox = $assert_session->fieldExists('Published', $form)->getOuterHtml();
$this->assertGreaterThan(strpos($form_html, $name_field), strpos($form_html, $test_source_field));
$this->assertGreaterThan(strpos($form_html, $test_source_field), strpos($form_html, $vertical_tabs));
// The "Published" checkbox should be the last element.
$this->assertGreaterThan(strpos($form_html, $date_field), strpos($form_html, $published_checkbox));
// Check that a new type with the same machine name cannot be created.
$this->drupalGet('admin/structure/media/add');
$page->fillField('label', $label);
$session->wait(5000, "jQuery('.machine-name-value').text() === '{$mediaTypeMachineName}'");
$page->selectFieldOption('Media source', 'test');
$assert_session->assertWaitOnAjaxRequest();
$page->pressButton('Save');
$assert_session->pageTextContains('The machine-readable name is already in use. It must be unique.');
}
/**
* Tests creation of media type, reusing an existing source field.
*/
public function testMediaTypeCreationReuseSourceField(): void {
$session = $this->getSession();
$page = $session->getPage();
$assert_session = $this->assertSession();
// Create a new media type, which should create a new field we can reuse.
$this->drupalGet('/admin/structure/media/add');
// Choose the source plugin before setting the label and machine name.
$page->selectFieldOption('Media source', 'test');
$result = $assert_session->waitForElementVisible('css', 'fieldset[data-drupal-selector="edit-source-configuration"]');
$this->assertNotEmpty($result);
$page->fillField('label', 'Pastafazoul');
$session->wait(5000, "jQuery('.machine-name-value').text() === 'pastafazoul'");
$page->pressButton('Save');
$label = 'Type reusing Default Field';
$mediaTypeMachineName = str_replace(' ', '_', strtolower($label));
$this->drupalGet('admin/structure/media/add');
// Select the media source used by our media type. Do this before setting
// the label and machine name.
$assert_session->fieldExists('Media source');
$assert_session->optionExists('Media source', 'test');
$page->selectFieldOption('Media source', 'test');
$result = $assert_session->waitForElementVisible('css', 'fieldset[data-drupal-selector="edit-source-configuration"]');
$this->assertNotEmpty($result);
// Select the existing field for re-use.
$page->selectFieldOption('source_configuration[source_field]', 'field_media_test');
// Fill in a label to the media type.
$page->fillField('label', $label);
// Wait for machine name generation. Default: waitUntilVisible(), does not
// work properly.
$session->wait(5000, "jQuery('.machine-name-value').text() === '{$mediaTypeMachineName}'");
$page->pressButton('Save');
// Check that no new fields were created.
$this->drupalGet("admin/structure/media/manage/{$mediaTypeMachineName}/fields");
// The reused field should be present...
$assert_session->pageTextContains('field_media_test');
// ...not a new, unique one.
$assert_session->pageTextNotContains('field_media_test_1');
}
}

View File

@ -0,0 +1,214 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\FunctionalJavascript;
use Drupal\field\FieldConfigInterface;
use Drupal\media\Entity\Media;
use Drupal\media\Entity\MediaType;
use Drupal\media\MediaSourceInterface;
/**
* Ensures that media UI works correctly.
*
* @group media
*/
class MediaUiJavascriptTest extends MediaJavascriptTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'block',
'media_test_source',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* The test media type.
*
* @var \Drupal\media\MediaTypeInterface
*/
protected $testMediaType;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->drupalPlaceBlock('local_actions_block');
$this->drupalPlaceBlock('local_tasks_block');
}
/**
* Tests a media type administration.
*/
public function testMediaTypes(): void {
$session = $this->getSession();
$page = $session->getPage();
$assert_session = $this->assertSession();
$this->drupalGet('admin/structure/media');
$assert_session->pageTextContains('No media types available. Add media type.');
$assert_session->linkExists('Add media type');
// Test the creation of a media type using the UI.
$name = $this->randomMachineName();
$description = $this->randomMachineName();
$this->drupalGet('admin/structure/media/add');
$page->fillField('label', $name);
$machine_name = strtolower($name);
$this->assertJsCondition("jQuery('.machine-name-value').html() == '$machine_name'");
$page->selectFieldOption('source', 'test');
$this->assertJsCondition("jQuery('.form-item-source-configuration-test-config-value').length > 0");
$page->fillField('description', $description);
$page->pressButton('Save and manage fields');
// The wait prevents intermittent test failures.
$result = $assert_session->waitForLink('Create a new field');
$this->assertNotEmpty($result);
$assert_session->addressEquals('admin/structure/media/manage/' . $machine_name . '/fields');
$assert_session->pageTextContains('The media type ' . $name . ' has been added.');
$this->drupalGet('admin/structure/media');
$assert_session->pageTextContains($name);
$assert_session->pageTextContains($description);
// We need to clear the statically cached field definitions to account for
// fields that have been created by API calls in this test, since they exist
// in a separate memory space from the web server.
$this->container->get('entity_field.manager')->clearCachedFieldDefinitions();
// Assert that the field and field storage were created.
$media_type = MediaType::load($machine_name);
$source = $media_type->getSource();
/** @var \Drupal\field\FieldConfigInterface $source_field */
$source_field = $source->getSourceFieldDefinition($media_type);
$this->assertInstanceOf(FieldConfigInterface::class, $source_field);
$this->assertFalse($source_field->isNew(), 'Source field was saved.');
/** @var \Drupal\field\FieldStorageConfigInterface $storage */
$storage = $source_field->getFieldStorageDefinition();
$this->assertFalse($storage->isNew(), 'Source field storage definition was saved.');
$this->assertFalse($storage->isLocked(), 'Source field storage definition was not locked.');
/** @var \Drupal\media\MediaTypeInterface $media_type_storage */
$media_type_storage = $this->container->get('entity_type.manager')->getStorage('media_type');
$this->testMediaType = $media_type_storage->load(strtolower($name));
// Check if all action links exist.
$assert_session->linkByHrefExists('admin/structure/media/add');
$assert_session->linkByHrefExists('admin/structure/media/manage/' . $this->testMediaType->id());
$assert_session->linkByHrefExists('admin/structure/media/manage/' . $this->testMediaType->id() . '/fields');
$assert_session->linkByHrefExists('admin/structure/media/manage/' . $this->testMediaType->id() . '/form-display');
$assert_session->linkByHrefExists('admin/structure/media/manage/' . $this->testMediaType->id() . '/display');
// Assert that fields have expected values before editing.
$page->clickLink('Edit');
$assert_session->fieldValueEquals('label', $name);
$assert_session->fieldValueEquals('description', $description);
$assert_session->fieldValueEquals('source', 'test');
$assert_session->fieldValueEquals('label', $name);
$assert_session->checkboxNotChecked('edit-options-new-revision');
$assert_session->checkboxChecked('edit-options-status');
$assert_session->checkboxNotChecked('edit-options-queue-thumbnail-downloads');
$assert_session->pageTextContains('Create new revision');
$assert_session->pageTextContains('Automatically create new revisions. Users with the "Administer media" permission will be able to override this option.');
$assert_session->pageTextContains('Download thumbnails via a queue.');
$assert_session->pageTextContains('Media will be automatically published when created.');
$assert_session->pageTextContains('Media sources can provide metadata fields such as title, caption, size information, credits, etc. Media can automatically save this metadata information to entity fields, which can be configured below. Information will only be mapped if the entity field is empty.');
// Try to change media type and check if new configuration sub-form appears.
$page->selectFieldOption('source', 'test');
$result = $assert_session->waitForElementVisible('css', 'fieldset[data-drupal-selector="edit-source-configuration"]');
$this->assertNotEmpty($result);
$assert_session->fieldExists('Test config value');
$assert_session->fieldValueEquals('Test config value', 'This is default value.');
$assert_session->fieldExists('Attribute 1');
$assert_session->fieldExists('Attribute 2');
// Test if the edit machine name is not editable.
$assert_session->fieldDisabled('Machine-readable name');
// Edit and save media type form fields with new values.
$new_name = $this->randomMachineName();
$new_description = $this->randomMachineName();
$page->fillField('label', $new_name);
$page->fillField('description', $new_description);
$page->selectFieldOption('source', 'test');
$page->fillField('Test config value', 'This is new config value.');
$page->selectFieldOption('field_map[attribute_1]', 'name');
$page->checkField('options[new_revision]');
$page->uncheckField('options[status]');
$page->checkField('options[queue_thumbnail_downloads]');
$page->pressButton('Save');
// The wait prevents intermittent test failures.
$result = $assert_session->waitForLink('Add media type');
$this->assertNotEmpty($result);
$assert_session->addressEquals('admin/structure/media');
$assert_session->pageTextContains("The media type $new_name has been updated.");
// Test if edit worked and if new field values have been saved as expected.
$this->drupalGet('admin/structure/media/manage/' . $this->testMediaType->id());
$assert_session->fieldValueEquals('label', $new_name);
$assert_session->fieldValueEquals('description', $new_description);
$assert_session->fieldValueEquals('source', 'test');
$assert_session->checkboxChecked('options[new_revision]');
$assert_session->checkboxNotChecked('options[status]');
$assert_session->checkboxChecked('options[queue_thumbnail_downloads]');
$assert_session->fieldValueEquals('Test config value', 'This is new config value.');
$assert_session->fieldValueEquals('Attribute 1', 'name');
$assert_session->fieldValueEquals('Attribute 2', MediaSourceInterface::METADATA_FIELD_EMPTY);
/** @var \Drupal\media\MediaTypeInterface $loaded_media_type */
$loaded_media_type = $this->container->get('entity_type.manager')
->getStorage('media_type')
->load($this->testMediaType->id());
$this->assertSame($loaded_media_type->id(), $this->testMediaType->id());
$this->assertSame($loaded_media_type->label(), $new_name);
$this->assertSame($loaded_media_type->getDescription(), $new_description);
$this->assertSame($loaded_media_type->getSource()->getPluginId(), 'test');
$this->assertSame($loaded_media_type->getSource()->getConfiguration()['test_config_value'], 'This is new config value.');
$this->assertTrue($loaded_media_type->shouldCreateNewRevision());
$this->assertTrue($loaded_media_type->thumbnailDownloadsAreQueued());
$this->assertFalse($loaded_media_type->getStatus());
$this->assertSame($loaded_media_type->getFieldMap(), ['attribute_1' => 'name']);
// We need to clear the statically cached field definitions to account for
// fields that have been created by API calls in this test, since they exist
// in a separate memory space from the web server.
$this->container->get('entity_field.manager')->clearCachedFieldDefinitions();
// Test that a media item being created with default status to "FALSE",
// will be created unpublished.
/** @var \Drupal\media\MediaInterface $unpublished_media */
$unpublished_media = Media::create(['name' => 'unpublished test media', 'bundle' => $loaded_media_type->id()]);
$this->assertFalse($unpublished_media->isPublished());
$unpublished_media->delete();
// Tests media type delete form.
$page->clickLink('Delete');
$assert_session->assertWaitOnAjaxRequest();
$this->assertNotEmpty($assert_session->waitForElementVisible('css', '#drupal-modal'));
$assert_session->addressEquals('admin/structure/media/manage/' . $this->testMediaType->id());
$this->click('.ui-dialog button:contains("Delete")');
$assert_session->addressEquals('admin/structure/media');
$assert_session->pageTextContains('The media type ' . $new_name . ' has been deleted.');
// Test that the system for preventing the deletion of media types works
// (they cannot be deleted if there is media content of that type/bundle).
$media_type2 = $this->createMediaType('test');
$label2 = $media_type2->label();
$media = Media::create(['name' => 'lorem ipsum', 'bundle' => $media_type2->id()]);
$media->save();
$this->drupalGet('admin/structure/media/manage/' . $media_type2->id());
$page->clickLink('Delete');
$assert_session->assertWaitOnAjaxRequest();
$this->assertNotEmpty($assert_session->waitForElementVisible('css', '#drupal-modal'));
$assert_session->addressEquals('admin/structure/media/manage/' . $media_type2->id());
$assert_session->elementNotExists('css', '.ui-dialog button:contains("Delete")');
$assert_session->pageTextContains("$label2 is used by 1 media item on your site. You can not remove this media type until you have removed all of the $label2 media items.");
}
}

View File

@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\FunctionalJavascript;
use Drupal\views\Views;
/**
* Tests the media entity type integration into the wizard.
*
* @group media
*
* @see \Drupal\media\Plugin\views\wizard\Media
* @see \Drupal\media\Plugin\views\wizard\MediaRevision
*/
class MediaViewsWizardTest extends MediaJavascriptTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests adding a view of media.
*/
public function testMediaWizard(): void {
$session = $this->getSession();
$page = $session->getPage();
$assert_session = $this->assertSession();
$this->createMediaType('test');
$view_id = $this->randomMachineName(16);
$this->drupalGet('admin/structure/views/add');
$page->fillField('label', $view_id);
$this->waitUntilVisible('.machine-name-value');
$page->selectFieldOption('show[wizard_key]', 'media');
$result = $assert_session->waitForElementVisible('css', 'select[data-drupal-selector="edit-show-type"]');
$this->assertNotEmpty($result);
$page->checkField('page[create]');
$page->fillField('page[path]', $this->randomMachineName(16));
$page->pressButton('Save and edit');
$this->assertSame($session->getCurrentUrl(), $this->baseUrl . '/admin/structure/views/view/' . $view_id);
$view = Views::getView($view_id);
$view->initHandlers();
$row = $view->display_handler->getOption('row');
$this->assertSame($row['type'], 'fields');
// Check for the default filters.
$this->assertSame($view->filter['status']->table, 'media_field_data');
$this->assertSame($view->filter['status']->field, 'status');
$this->assertSame($view->filter['status']->value, '1');
// Check for the default fields.
$this->assertSame($view->field['name']->table, 'media_field_data');
$this->assertSame($view->field['name']->field, 'name');
}
/**
* Tests adding a view of media revisions.
*/
public function testMediaRevisionWizard(): void {
$session = $this->getSession();
$page = $session->getPage();
$assert_session = $this->assertSession();
$view_id = $this->randomMachineName(16);
$this->drupalGet('admin/structure/views/add');
$page->fillField('label', $view_id);
$this->waitUntilVisible('.machine-name-value');
$page->selectFieldOption('show[wizard_key]', 'media_revision');
$assert_session->assertWaitOnAjaxRequest();
$page->checkField('page[create]');
$page->fillField('page[path]', $this->randomMachineName(16));
$page->pressButton('Save and edit');
$this->assertSame($session->getCurrentUrl(), $this->baseUrl . '/admin/structure/views/view/' . $view_id);
$view = Views::getView($view_id);
$view->initHandlers();
$row = $view->display_handler->getOption('row');
$this->assertSame($row['type'], 'fields');
// Check for the default filters.
$this->assertSame($view->filter['status']->table, 'media_field_revision');
$this->assertSame($view->filter['status']->field, 'status');
$this->assertSame($view->filter['status']->value, '1');
// Check for the default fields.
$this->assertSame($view->field['name']->table, 'media_field_revision');
$this->assertSame($view->field['name']->field, 'name');
$this->assertSame($view->field['changed']->table, 'media_field_revision');
$this->assertSame($view->field['changed']->field, 'changed');
}
}

View File

@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Kernel;
use Drupal\Core\Config\Action\ConfigActionManager;
use Drupal\Core\Extension\ModuleInstallerInterface;
use Drupal\KernelTests\KernelTestBase;
use Drupal\media\Entity\MediaType;
/**
* @group media
*/
class ConfigActionsTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['media'];
/**
* The configuration action manager.
*/
private readonly ConfigActionManager $configActionManager;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->container->get(ModuleInstallerInterface::class)->install([
'media_test_type',
]);
$this->configActionManager = $this->container->get('plugin.manager.config_action');
}
/**
* Tests the application of configuration actions on a media type.
*/
public function testConfigActions(): void {
$media_type = MediaType::load('test');
$this->assertSame('Test type.', $media_type->getDescription());
$this->assertSame(['metadata_attribute' => 'field_attribute_config_test'], $media_type->getFieldMap());
$this->configActionManager->applyAction(
'entity_method:media.type:setDescription',
$media_type->getConfigDependencyName(),
'Changed by a config action...',
);
$this->configActionManager->applyAction(
'entity_method:media.type:setFieldMap',
$media_type->getConfigDependencyName(),
['foo' => 'baz'],
);
$media_type = MediaType::load('test');
$this->assertSame('Changed by a config action...', $media_type->getDescription());
$this->assertSame(['foo' => 'baz'], $media_type->getFieldMap());
}
}

View File

@ -0,0 +1,780 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Kernel;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Access\AccessResultInterface;
use Drupal\media\Entity\Media;
use Drupal\Tests\user\Traits\UserCreationTrait;
/**
* Tests the media access control handler.
*
* @group media
*
* @coversDefaultClass \Drupal\media\MediaAccessControlHandler
*/
class MediaAccessControlHandlerTest extends MediaKernelTestBase {
use UserCreationTrait;
/**
* Tests the media access control handler.
*
* @param string[] $permissions
* The permissions that the user should be given.
* @param array $entity_values
* Initial values from which to create the media entity.
* @param string $operation
* The operation, one of 'view', 'update' or 'delete'.
* @param \Drupal\Core\Access\AccessResultInterface $expected_result
* Expected result.
* @param string[] $expected_cache_contexts
* Expected cache contexts.
* @param string[] $expected_cache_tags
* Expected cache tags.
* @param bool $is_latest_revision
* If FALSE, the media is historic revision.
*
* @covers ::checkAccess
* @dataProvider providerAccess
*/
public function testAccess(array $permissions, array $entity_values, string $operation, AccessResultInterface $expected_result, array $expected_cache_contexts, array $expected_cache_tags, bool $is_latest_revision): void {
/** @var \Drupal\Core\Entity\RevisionableStorageInterface $entityStorage $entity_storage */
$entity_storage = $this->container->get('entity_type.manager')->getStorage('media');
// Set a fixed ID so the type specific permissions match.
$media_type = $this->createMediaType('test', ['id' => 'test']);
$user = $this->createUser($permissions);
$entity_values += [
'status' => FALSE,
'uid' => $user->id(),
'bundle' => $media_type->id(),
];
$entity = Media::create($entity_values);
$entity->save();
$load_revision_id = NULL;
if (!$is_latest_revision) {
$load_revision_id = $entity->getRevisionId();
// Set up for a new revision to be saved.
$entity = $entity_storage->createRevision($entity);
}
$entity->save();
// Reload a previous revision.
if ($load_revision_id !== NULL) {
$entity = $entity_storage->loadRevision($load_revision_id);
}
/** @var \Drupal\Core\Entity\EntityAccessControlHandlerInterface $access_handler */
$access_handler = $this->container->get('entity_type.manager')->getAccessControlHandler('media');
$this->assertAccess($expected_result, $expected_cache_contexts, $expected_cache_tags, $access_handler->access($entity, $operation, $user, TRUE));
}
/**
* @param string[] $permissions
* User permissions.
* @param \Drupal\Core\Access\AccessResultInterface $expected_result
* Expected result.
* @param string[] $expected_cache_contexts
* Expected cache contexts.
* @param string[] $expected_cache_tags
* Expected cache tags.
*
* @covers ::checkCreateAccess
* @dataProvider providerCreateAccess
*/
public function testCreateAccess(array $permissions, AccessResultInterface $expected_result, array $expected_cache_contexts, array $expected_cache_tags): void {
$user = $this->createUser($permissions);
/** @var \Drupal\Core\Entity\EntityAccessControlHandlerInterface $access_handler */
$access_handler = $this->container->get('entity_type.manager')->getAccessControlHandler('media');
$this->assertAccess($expected_result, $expected_cache_contexts, $expected_cache_tags, $access_handler->createAccess('test', $user, [], TRUE));
}
/**
* Asserts an access result.
*
* @param \Drupal\Core\Access\AccessResultInterface $expected_access_result
* The expected access result.
* @param string[] $expected_cache_contexts
* Expected contexts.
* @param string[] $expected_cache_tags
* Expected cache tags.
* @param \Drupal\Core\Access\AccessResultInterface $actual
* The actual access result.
*
* @internal
*/
protected function assertAccess(AccessResultInterface $expected_access_result, array $expected_cache_contexts, array $expected_cache_tags, AccessResultInterface $actual): void {
$this->assertSame($expected_access_result->isAllowed(), $actual->isAllowed());
$this->assertSame($expected_access_result->isForbidden(), $actual->isForbidden());
$this->assertSame($expected_access_result->isNeutral(), $actual->isNeutral());
$actual_cache_contexts = $actual->getCacheContexts();
sort($expected_cache_contexts);
sort($actual_cache_contexts);
$this->assertSame($expected_cache_contexts, $actual_cache_contexts);
$actual_cache_tags = $actual->getCacheTags();
sort($expected_cache_tags);
sort($actual_cache_tags);
$this->assertSame($expected_cache_tags, $actual_cache_tags);
}
/**
* Data provider for testAccess().
*
* @return array
* The data sets to test.
*/
public static function providerAccess() {
$test_data = [];
// Check published / unpublished media access for a user owning the media
// item without permissions.
$test_data['owner, no permissions / published / view'] = [
[],
['status' => TRUE],
'view',
AccessResult::neutral(),
['user.permissions'],
['media:1'],
TRUE,
];
$test_data['owner, no permissions / published / update'] = [
[],
['status' => TRUE],
'update',
AccessResult::neutral(),
['user.permissions'],
[],
TRUE,
];
$test_data['owner, no permissions / published / delete'] = [
[],
['status' => TRUE],
'delete',
AccessResult::neutral(),
['user.permissions'],
[],
TRUE,
];
$test_data['owner, no permissions / unpublished / view'] = [
[],
[],
'view',
AccessResult::neutral(),
['user.permissions'],
['media:1'],
TRUE,
];
$test_data['owner, no permissions / unpublished / update'] = [
[],
[],
'update',
AccessResult::neutral(),
['user.permissions'],
[],
TRUE,
];
$test_data['owner, no permissions / unpublished / delete'] = [
[],
[],
'delete',
AccessResult::neutral(),
['user.permissions'],
[],
TRUE,
];
// Check published / unpublished media access for a user not owning the
// media item without permissions.
$test_data['not owner, no permissions / published / view'] = [
[],
['uid' => 0, 'status' => TRUE],
'view',
AccessResult::neutral(),
['user.permissions'],
['media:1'],
TRUE,
];
$test_data['not owner, no permissions / published / update'] = [
[],
['uid' => 0, 'status' => TRUE],
'update',
AccessResult::neutral(),
['user.permissions'],
[],
TRUE,
];
$test_data['not owner, no permissions / published / delete'] = [
[],
['uid' => 0, 'status' => TRUE],
'delete',
AccessResult::neutral(),
['user.permissions'],
[],
TRUE,
];
$test_data['not owner, no permissions / unpublished / view'] = [
[],
['uid' => 0],
'view',
AccessResult::neutral(),
['user.permissions'],
['media:1'],
TRUE,
];
$test_data['not owner, no permissions / unpublished / update'] = [
[],
['uid' => 0],
'update',
AccessResult::neutral(),
['user.permissions'],
[],
TRUE,
];
$test_data['not owner, no permissions / unpublished / delete'] = [
[],
['uid' => 0],
'delete',
AccessResult::neutral(),
['user.permissions'],
[],
TRUE,
];
// Check published / unpublished media access for a user owning the media
// item with only the 'view media' permission.
$test_data['owner, can view media / published / view'] = [
['view media'],
['status' => TRUE],
'view',
AccessResult::allowed(),
['user.permissions'],
['media:1'],
TRUE,
];
$test_data['owner, can view media / published / update'] = [
['view media'],
['status' => TRUE],
'update',
AccessResult::neutral(),
['user.permissions'],
[],
TRUE,
];
$test_data['owner, can view media / published / delete'] = [
['view media'],
['status' => TRUE],
'delete',
AccessResult::neutral(),
['user.permissions'],
[],
TRUE,
];
$test_data['owner, can view media / unpublished / view'] = [
['view media'],
[],
'view',
AccessResult::neutral(),
['user.permissions'],
['media:1'],
TRUE,
];
$test_data['owner, can view media / unpublished / update'] = [
['view media'],
[],
'update',
AccessResult::neutral(),
['user.permissions'],
[],
TRUE,
];
$test_data['owner, can view media / unpublished / delete'] = [
['view media'],
[],
'delete',
AccessResult::neutral(),
['user.permissions'],
[],
TRUE,
];
// Check published / unpublished media access for a user not owning the
// media item with only the 'view media' permission.
$test_data['not owner, can view media / published / view'] = [
['view media'],
['uid' => 0, 'status' => TRUE],
'view',
AccessResult::allowed(),
['user.permissions'],
['media:1'],
TRUE,
];
$test_data['not owner, can view media / published / update'] = [
['view media'],
['uid' => 0, 'status' => TRUE],
'update',
AccessResult::neutral(),
['user.permissions'],
[],
TRUE,
];
$test_data['not owner, can view media / published / delete'] = [
['view media'],
['uid' => 0, 'status' => TRUE],
'delete',
AccessResult::neutral(),
['user.permissions'],
[],
TRUE,
];
$test_data['not owner, can view media / unpublished / view'] = [
['view media'],
['uid' => 0],
'view',
AccessResult::neutral(),
['user.permissions'],
['media:1'],
TRUE,
];
$test_data['not owner, can view media / unpublished / update'] = [
['view media'],
['uid' => 0],
'update',
AccessResult::neutral(),
['user.permissions'],
[],
TRUE,
];
$test_data['not owner, can view media / unpublished / delete'] = [
['view media'],
['uid' => 0],
'delete',
AccessResult::neutral(),
['user.permissions'],
[],
TRUE,
];
// Check published / unpublished media access for a user owning the media
// item with the 'view media' and 'view own unpublished' permission.
$test_data['owner, can view own unpublished media / published / view'] = [
['view media', 'view own unpublished media'],
['status' => TRUE],
'view',
AccessResult::allowed(),
['user.permissions'],
['media:1'],
TRUE,
];
$test_data['owner, can view own unpublished media / published / update'] = [
['view media', 'view own unpublished media'],
['status' => TRUE],
'update',
AccessResult::neutral(),
['user.permissions'],
[],
TRUE,
];
$test_data['owner, can view own unpublished media / published / delete'] = [
['view media', 'view own unpublished media'],
['status' => TRUE],
'delete',
AccessResult::neutral(),
['user.permissions'],
[],
TRUE,
];
$test_data['owner, can view own unpublished media / unpublished / view'] = [
['view media', 'view own unpublished media'],
[],
'view',
AccessResult::allowed(),
['user.permissions', 'user'],
['media:1'],
TRUE,
];
$test_data['owner, can view own unpublished media / unpublished / update'] = [
['view media', 'view own unpublished media'],
[],
'update',
AccessResult::neutral(),
['user.permissions'],
[],
TRUE,
];
$test_data['owner, can view own unpublished media / unpublished / delete'] = [
['view media', 'view own unpublished media'],
[],
'delete',
AccessResult::neutral(),
['user.permissions'],
[],
TRUE,
];
// Check published / unpublished media access for a user not owning the
// media item with the 'view media' and 'view own unpublished' permission.
$test_data['not owner, can view own unpublished media / published / view'] = [
['view media', 'view own unpublished media'],
['uid' => 0, 'status' => TRUE],
'view',
AccessResult::allowed(),
['user.permissions'],
['media:1'],
TRUE,
];
$test_data['not owner, can view own unpublished media / published / update'] = [
['view media', 'view own unpublished media'],
['uid' => 0, 'status' => TRUE],
'update',
AccessResult::neutral(),
['user.permissions'],
[],
TRUE,
];
$test_data['not owner, can view own unpublished media / published / delete'] = [
['view media', 'view own unpublished media'],
['uid' => 0, 'status' => TRUE],
'delete',
AccessResult::neutral(),
['user.permissions'],
[],
TRUE,
];
$test_data['not owner, can view own unpublished media / unpublished / view'] = [
['view media', 'view own unpublished media'],
['uid' => 0],
'view',
AccessResult::neutral(),
['user.permissions', 'user'],
['media:1'],
TRUE,
];
$test_data['not owner, can view own unpublished media / unpublished / update'] = [
['view media', 'view own unpublished media'],
['uid' => 0],
'update',
AccessResult::neutral(),
['user.permissions'],
[],
TRUE,
];
$test_data['not owner, can view own unpublished media / unpublished / delete'] = [
['view media', 'view own unpublished media'],
['uid' => 0],
'delete',
AccessResult::neutral(),
['user.permissions'],
[],
TRUE,
];
// View all revisions:
$test_data['view all revisions:none'] = [
[],
[],
'view all revisions',
AccessResult::neutral(),
['user.permissions'],
[],
TRUE,
];
$test_data['admins can view all revisions'] = [
['administer media'],
[],
'view all revisions',
AccessResult::allowed(),
['user.permissions'],
[],
TRUE,
];
$test_data['view all revisions with view bundle permission'] = [
['view any test media revisions', 'view media'],
['status' => TRUE],
'view all revisions',
AccessResult::allowed(),
['user.permissions'],
['media:1'],
TRUE,
];
// Revert revisions:
$test_data['revert a latest revision with no permissions'] = [
[],
[],
'revert',
AccessResult::forbidden(),
[],
[],
TRUE,
];
$test_data['revert a historical revision with no permissions'] = [
[],
[],
'revert',
AccessResult::neutral(),
['user.permissions'],
['media:1'],
FALSE,
];
$test_data['revert latest revision with administer media permission'] = [
['administer media'],
[],
'revert',
AccessResult::forbidden(),
[],
[],
TRUE,
];
$test_data['revert a historical revision with administer media permission'] = [
['administer media'],
[],
'revert',
AccessResult::allowed(),
['user.permissions'],
[],
FALSE,
];
$test_data['revert a latest revision with revert bundle permission'] = [
['revert any test media revisions'],
[],
'revert',
AccessResult::forbidden(),
[],
[],
TRUE,
];
$test_data['revert a historical revision with revert bundle permission'] = [
['revert any test media revisions'],
[],
'revert',
AccessResult::allowed(),
['user.permissions'],
['media:1'],
FALSE,
];
// Delete revisions:
$test_data['delete a latest revision with no permission'] = [
[],
[],
'delete revision',
AccessResult::forbidden(),
[],
[],
TRUE,
];
$test_data['delete a historical revision with no permission'] = [
[],
[],
'delete revision',
AccessResult::neutral(),
['user.permissions'],
['media:1'],
FALSE,
];
$test_data['delete a latest revision with administer media permission'] = [
['administer media'],
[],
'delete revision',
AccessResult::forbidden(),
[],
[],
TRUE,
];
$test_data['delete a historical revision with administer media permission'] = [
['administer media'],
[],
'delete revision',
AccessResult::allowed(),
['user.permissions'],
[],
FALSE,
];
$test_data['delete a latest revision with delete bundle permission'] = [
['delete any test media revisions'],
[],
'delete revision',
AccessResult::forbidden(),
[],
[],
TRUE,
];
$test_data['delete a historical revision with delete bundle permission'] = [
['delete any test media revisions'],
[],
'delete revision',
AccessResult::allowed(),
['user.permissions'],
['media:1'],
FALSE,
];
return $test_data;
}
/**
* Data provider for testCreateAccess().
*
* @return array
* The data sets to test.
*/
public static function providerCreateAccess() {
$test_data = [];
// Check create access for a user without permissions.
$test_data['user, no permissions / create'] = [
[],
AccessResult::neutral()->setReason("The following permissions are required: 'administer media' OR 'create media'."),
['user.permissions'],
[],
];
// Check create access for a user with the 'view media' permission.
$test_data['user, can view media / create'] = [
[
'view media',
],
AccessResult::neutral("The following permissions are required: 'administer media' OR 'create media'."),
['user.permissions'],
[],
];
// Check create access for a user with the 'view media' and 'view own
// unpublished media' permission.
$test_data['user, can view own unpublished media / create'] = [
[
'view media',
'view own unpublished media',
],
AccessResult::neutral("The following permissions are required: 'administer media' OR 'create media'."),
['user.permissions'],
[],
];
// Check create access for a user with the 'view media', 'view own
// unpublished media', 'update any media' and 'delete any media' permission.
$test_data['user, can view own unpublished media and update or delete any media / create'] = [
[
'view media',
'view own unpublished media',
'update any media',
'delete any media',
],
AccessResult::neutral("The following permissions are required: 'administer media' OR 'create media'."),
['user.permissions'],
[],
];
// Check create access for a user with the 'view media', 'view own
// unpublished media', 'update media' and 'delete media' permission.
$test_data['user, can view own unpublished media and update or delete own media / create'] = [
[
'view media',
'view own unpublished media',
'update media',
'delete media',
],
AccessResult::neutral("The following permissions are required: 'administer media' OR 'create media'."),
['user.permissions'],
[],
];
// Check create access for a user with the 'view media', 'view own
// unpublished media', 'update any media', 'delete any media', 'update
// media' and 'delete media' permission.
$test_data['user, can view own unpublished media and update or delete all media / create'] = [
[
'view media',
'view own unpublished media',
'update any media',
'delete any media',
'update media',
'delete media',
],
AccessResult::neutral("The following permissions are required: 'administer media' OR 'create media'."),
['user.permissions'],
[],
];
// Check create access for a user with all media permissions except 'create
// media' or 'administer media'.
$test_data['user, can not create or administer media / create'] = [
[
'access media overview',
'view media',
'view own unpublished media',
'update any media',
'delete any media',
'update media',
'delete media',
],
AccessResult::neutral("The following permissions are required: 'administer media' OR 'create media'."),
['user.permissions'],
[],
];
// Check create access for a user with the 'create media' permission.
$test_data['user, can create media / create'] = [
[
'create media',
],
AccessResult::allowed(),
['user.permissions'],
[],
];
// Check create access for a user with the 'administer media' permission.
$test_data['user, can administer media / create'] = [
[
'administer media',
],
AccessResult::allowed(),
['user.permissions'],
[],
];
return $test_data;
}
/**
* Tests access to the revision log field.
*/
public function testRevisionLogFieldAccess(): void {
$admin = $this->createUser([
'administer media',
'view media',
]);
$editor = $this->createUser([
'view all media revisions',
'view media',
]);
$viewer = $this->createUser([
'view media',
]);
$media_type = $this->createMediaType('test', [
'id' => 'test',
]);
$entity = Media::create([
'status' => TRUE,
'bundle' => $media_type->id(),
]);
$entity->save();
$this->assertTrue($entity->get('revision_log_message')->access('view', $admin));
$this->assertTrue($entity->get('revision_log_message')->access('view', $editor));
$this->assertFalse($entity->get('revision_log_message')->access('view', $viewer));
$entity->setUnpublished()->save();
\Drupal::entityTypeManager()->getAccessControlHandler('media')->resetCache();
$this->assertFalse($entity->get('revision_log_message')->access('view', $viewer));
}
}

View File

@ -0,0 +1,83 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Kernel;
use Drupal\media\Entity\Media;
use Drupal\media\Entity\MediaType;
use Drupal\media\MediaInterface;
use Drupal\media\MediaTypeInterface;
use Drupal\user\Entity\Role;
use Drupal\user\Entity\User;
/**
* Tests creation of media types and media items.
*
* @group media
*/
class MediaCreationTest extends MediaKernelTestBase {
/**
* Tests creating a media type programmatically.
*/
public function testMediaTypeCreation(): void {
$media_type_storage = $this->container->get('entity_type.manager')->getStorage('media_type');
$this->assertInstanceOf(MediaTypeInterface::class, MediaType::load($this->testMediaType->id()));
// Test a media type created from default configuration.
$this->container->get('module_installer')->install(['media_test_type']);
$test_media_type = $media_type_storage->load('test');
$this->assertInstanceOf(MediaTypeInterface::class, $test_media_type);
$this->assertSame('Test type', $test_media_type->get('label'), 'Could not assure the correct type name.');
$this->assertSame('Test type.', $test_media_type->get('description'), 'Could not assure the correct type description.');
$this->assertSame('test', $test_media_type->get('source'), 'Could not assure the correct media source.');
// Source field is not set on the media source, but it should never
// be created automatically when a config is being imported.
$this->assertSame(['source_field' => '', 'test_config_value' => 'Foo'], $test_media_type->get('source_configuration'), 'Could not assure the correct media source configuration.');
$this->assertSame(['metadata_attribute' => 'field_attribute_config_test'], $test_media_type->get('field_map'), 'Could not assure the correct field map.');
// Check the Media Type access handler behavior.
// We grant access to the 'view label' operation to all users having
// permission to 'view media'.
$user1 = User::create([
'name' => 'username1',
'status' => 1,
]);
$user1->save();
$user2 = User::create([
'name' => 'username2',
'status' => 1,
]);
$user2->save();
$role = Role::create([
'id' => 'role1',
'label' => 'role1',
]);
$role->grantPermission('view media')->trustData()->save();
$user2->addRole($role->id());
$this->assertFalse($test_media_type->access('view label', $user1));
$this->assertTrue($test_media_type->access('view label', $user2));
}
/**
* Tests creating a media item programmatically.
*/
public function testMediaEntityCreation(): void {
$media = Media::create([
'bundle' => $this->testMediaType->id(),
'name' => 'Unnamed',
'field_media_test' => 'Nation of sheep, ruled by wolves, owned by pigs.',
]);
$media->save();
$this->assertNotInstanceOf(MediaInterface::class, Media::load(rand(1000, 9999)));
$this->assertInstanceOf(MediaInterface::class, Media::load($media->id()));
$this->assertSame($this->testMediaType->id(), $media->bundle(), 'The media item was not created with the correct type.');
$this->assertSame('Unnamed', $media->getName(), 'The media item was not created with the correct name.');
$source_field_name = $media->bundle->entity->getSource()->getSourceFieldDefinition($media->bundle->entity)->getName();
$this->assertSame('Nation of sheep, ruled by wolves, owned by pigs.', $media->get($source_field_name)->value, 'Source returns incorrect source field value.');
}
}

View File

@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Kernel;
/**
* Tests that media embed disables certain integrations.
*
* @coversDefaultClass \Drupal\media\Plugin\Filter\MediaEmbed
* @group media
*/
class MediaEmbedFilterDisabledIntegrationsTest extends MediaEmbedFilterTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'contextual',
// @see media_test_embed_entity_view_alter()
'media_test_embed',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->container->get('current_user')
->getAccount()
->addRole($this->drupalCreateRole([
'access contextual links',
]));
}
/**
* @covers ::renderMedia
* @covers ::disableContextualLinks
*/
public function testDisabledIntegrations(): void {
$text = $this->createEmbedCode([
'data-entity-type' => 'media',
'data-entity-uuid' => static::EMBEDDED_ENTITY_UUID,
]);
$this->applyFilter($text);
$this->assertCount(1, $this->cssSelect('div[data-media-embed-test-view-mode]'));
$this->assertCount(0, $this->cssSelect('div[data-media-embed-test-view-mode].contextual-region'));
}
}

View File

@ -0,0 +1,511 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Kernel;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
use Drupal\field\Entity\FieldConfig;
/**
* @coversDefaultClass \Drupal\media\Plugin\Filter\MediaEmbed
* @group media
*/
class MediaEmbedFilterTest extends MediaEmbedFilterTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
// @see media_test_embed_entity_access()
// @see media_test_embed_entity_view_alter()
'media_test_embed',
];
/**
* Ensures media entities are rendered correctly.
*
* @dataProvider providerTestBasics
*/
public function testBasics(array $embed_attributes, $expected_view_mode, array $expected_attributes, CacheableMetadata $expected_cacheability): void {
$content = $this->createEmbedCode($embed_attributes);
$result = $this->applyFilter($content);
$this->assertCount(1, $this->cssSelect('div[data-media-embed-test-view-mode="' . $expected_view_mode . '"]'));
$this->assertHasAttributes($this->cssSelect('div[data-media-embed-test-view-mode="' . $expected_view_mode . '"]')[0], $expected_attributes);
$this->assertEqualsCanonicalizing($expected_cacheability->getCacheTags(), $result->getCacheTags());
$this->assertEqualsCanonicalizing($expected_cacheability->getCacheContexts(), $result->getCacheContexts());
$this->assertSame($expected_cacheability->getCacheMaxAge(), $result->getCacheMaxAge());
$this->assertSame(['library'], array_keys($result->getAttachments()));
$this->assertSame(['media/filter.caption'], $result->getAttachments()['library']);
}
/**
* Data provider for testBasics().
*/
public static function providerTestBasics() {
$default_cacheability = (new CacheableMetadata())
->setCacheTags([
'_media_test_embed_filter_access:media:1',
'_media_test_embed_filter_access:user:2',
'config:image.style.thumbnail',
'file:1',
'media:1',
'media_view',
'user:2',
])
->setCacheContexts(['timezone', 'user.permissions'])
->setCacheMaxAge(Cache::PERMANENT);
return [
'data-entity-uuid only ⇒ default view mode used' => [
[
'data-entity-type' => 'media',
'data-entity-uuid' => static::EMBEDDED_ENTITY_UUID,
],
EntityDisplayRepositoryInterface::DEFAULT_DISPLAY_MODE,
[],
$default_cacheability,
],
'data-entity-uuid + data-view-mode=full ⇒ specified view mode used' => [
[
'data-entity-type' => 'media',
'data-entity-uuid' => static::EMBEDDED_ENTITY_UUID,
'data-view-mode' => 'full',
],
'full',
[],
$default_cacheability,
],
'data-entity-uuid + data-view-mode=default ⇒ specified view mode used' => [
[
'data-entity-type' => 'media',
'data-entity-uuid' => static::EMBEDDED_ENTITY_UUID,
'data-view-mode' => EntityDisplayRepositoryInterface::DEFAULT_DISPLAY_MODE,
],
EntityDisplayRepositoryInterface::DEFAULT_DISPLAY_MODE,
[],
$default_cacheability,
],
'data-entity-uuid + data-view-mode=foobar ⇒ specified view mode used' => [
[
'data-entity-type' => 'media',
'data-entity-uuid' => static::EMBEDDED_ENTITY_UUID,
'data-view-mode' => 'foobar',
],
'foobar',
[],
(new CacheableMetadata())
->setCacheTags([
'_media_test_embed_filter_access:media:1',
'config:image.style.medium',
'file:1',
'media:1',
'media_view',
])
->setCacheContexts(['user.permissions'])
->setCacheMaxAge(Cache::PERMANENT),
],
'custom attributes are retained' => [
[
'data-foo' => 'bar',
'foo' => 'bar',
'data-entity-type' => 'media',
'data-entity-uuid' => static::EMBEDDED_ENTITY_UUID,
],
EntityDisplayRepositoryInterface::DEFAULT_DISPLAY_MODE,
[
'data-foo' => 'bar',
'foo' => 'bar',
],
$default_cacheability,
],
];
}
/**
* Tests that entity access is respected by embedding an unpublished entity.
*
* @dataProvider providerAccessUnpublished
*/
public function testAccessUnpublished($allowed_to_view_unpublished, $expected_rendered, CacheableMetadata $expected_cacheability, array $expected_attachments): void {
// Unpublish the embedded entity so we can test variations in behavior.
$this->embeddedEntity->setUnpublished()->save();
// Are we testing as a user who is allowed to view the embedded entity?
if ($allowed_to_view_unpublished) {
$this->container->get('current_user')
->getAccount()
->addRole($this->drupalCreateRole(['view own unpublished media']));
}
$content = $this->createEmbedCode([
'data-entity-type' => 'media',
'data-entity-uuid' => static::EMBEDDED_ENTITY_UUID,
]);
$result = $this->applyFilter($content);
if (!$expected_rendered) {
$this->assertEmpty($this->getRawContent());
}
else {
$this->assertCount(1, $this->cssSelect('div[data-media-embed-test-view-mode="default"]'));
}
$this->assertEqualsCanonicalizing($expected_cacheability->getCacheTags(), $result->getCacheTags());
$this->assertEqualsCanonicalizing($expected_cacheability->getCacheContexts(), $result->getCacheContexts());
$this->assertSame($expected_cacheability->getCacheMaxAge(), $result->getCacheMaxAge());
$this->assertSame($expected_attachments, $result->getAttachments());
}
/**
* Data provider for testAccessUnpublished().
*/
public static function providerAccessUnpublished() {
return [
'user cannot access embedded media' => [
FALSE,
FALSE,
(new CacheableMetadata())
->setCacheTags([
'_media_test_embed_filter_access:media:1',
'media:1',
'media_view',
])
->setCacheContexts(['user.permissions'])
->setCacheMaxAge(Cache::PERMANENT),
[],
],
'user can access embedded media' => [
TRUE,
TRUE,
(new CacheableMetadata())
->setCacheTags([
'_media_test_embed_filter_access:media:1',
'_media_test_embed_filter_access:user:2',
'config:image.style.thumbnail',
'file:1',
'media:1',
'media_view',
'user:2',
])
->setCacheContexts(['timezone', 'user', 'user.permissions'])
->setCacheMaxAge(Cache::PERMANENT),
['library' => ['media/filter.caption']],
],
];
}
/**
* @covers ::applyPerEmbedMediaOverrides
* @dataProvider providerOverridesAltAndTitle
*/
public function testOverridesAltAndTitle($title_field_property_enabled, array $expected_title_attributes): void {
// The `alt` field property is enabled by default, the `title` one is not.
if ($title_field_property_enabled) {
$source_field = FieldConfig::load('media.image.field_media_image');
$source_field->setSetting('title_field', TRUE);
$source_field->save();
}
$base = [
'data-entity-type' => 'media',
'data-entity-uuid' => static::EMBEDDED_ENTITY_UUID,
];
$input = $this->createEmbedCode($base);
$input .= $this->createEmbedCode([
'alt' => 'alt 1',
'title' => 'title 1',
] + $base);
$input .= $this->createEmbedCode([
'alt' => 'alt 2',
'title' => 'title 2',
] + $base);
$input .= $this->createEmbedCode([
'alt' => 'alt 3',
'title' => 'title 3',
] + $base);
$input .= $this->createEmbedCode([
'alt' => '""',
'title' => 'title 4',
] + $base);
$this->applyFilter($input);
$img_nodes = $this->cssSelect('img');
$this->assertCount(5, $img_nodes);
$this->assertHasAttributes($img_nodes[0], [
'alt' => 'default alt',
'title' => $expected_title_attributes[0],
]);
$this->assertHasAttributes($img_nodes[1], [
'alt' => 'alt 1',
'title' => $expected_title_attributes[1],
]);
$this->assertHasAttributes($img_nodes[2], [
'alt' => 'alt 2',
'title' => $expected_title_attributes[2],
]);
$this->assertHasAttributes($img_nodes[3], [
'alt' => 'alt 3',
'title' => $expected_title_attributes[3],
]);
$this->assertHasAttributes($img_nodes[4], [
'alt' => '',
'title' => $expected_title_attributes[4],
]);
}
/**
* Data provider for testOverridesAltAndTitle().
*/
public static function providerOverridesAltAndTitle() {
return [
'`title` field property disabled ⇒ `title` is not overridable' => [
FALSE,
[NULL, NULL, NULL, NULL, NULL],
],
'`title` field property enabled ⇒ `title` is overridable' => [
TRUE,
[NULL, 'title 1', 'title 2', 'title 3', 'title 4'],
],
];
}
/**
* Tests the indicator for missing entities.
*
* @dataProvider providerMissingEntityIndicator
*/
public function testMissingEntityIndicator($uuid, array $filter_ids, array $additional_attributes): void {
$content = $this->createEmbedCode([
'data-entity-type' => 'media',
'data-entity-uuid' => $uuid,
'data-view-mode' => 'foobar',
] + $additional_attributes);
// If the UUID being used in the embed is that of the sample entity, first
// assert that it currently results in a functional embed, then delete it.
if ($uuid === static::EMBEDDED_ENTITY_UUID) {
$result = $this->processText($content, 'en', $filter_ids);
$this->setRawContent($result->getProcessedText());
$this->assertCount(1, $this->cssSelect('div[data-media-embed-test-view-mode="foobar"]'));
$this->embeddedEntity->delete();
}
$result = $this->processText($content, 'en', $filter_ids);
$this->setRawContent($result->getProcessedText());
$this->assertCount(0, $this->cssSelect('div[data-media-embed-test-view-mode="foobar"]'));
$this->assertCount(1, $this->cssSelect('div.this-error-message-is-themeable'));
if (in_array('filter_align', $filter_ids, TRUE) && !empty($additional_attributes['data-align'])) {
$this->assertCount(1, $this->cssSelect('div.align-' . $additional_attributes['data-align']));
}
}
/**
* Data provider for testMissingEntityIndicator().
*/
public static function providerMissingEntityIndicator() {
return [
'invalid UUID' => [
'uuid' => 'invalidUUID',
'filter_ids' => [
'filter_align',
'filter_caption',
'media_embed',
],
'additional_attributes' => [],
],
'valid UUID but for a deleted entity' => [
'uuid' => static::EMBEDDED_ENTITY_UUID,
'filter_ids' => [
'filter_align',
'filter_caption',
'media_embed',
],
'additional_attributes' => [],
],
'invalid UUID; data-align attribute without filter_align enabled' => [
'uuid' => 'invalidUUID',
'filter_ids' => [
'filter_caption',
'media_embed',
],
'additional_attributes' => ['data-align' => 'right'],
],
'invalid UUID; data-align attribute with filter_align enabled' => [
'uuid' => 'invalidUUID',
'filter_ids' => [
'filter_align',
'filter_caption',
'media_embed',
],
'additional_attributes' => ['data-align' => 'left'],
],
'valid UUID but for a deleted entity; data-align attribute with filter_align enabled' => [
'uuid' => static::EMBEDDED_ENTITY_UUID,
'filter_ids' => [
'filter_align',
'filter_caption',
'media_embed',
],
'additional_attributes' => ['data-align' => 'center'],
],
];
}
/**
* Tests that only <drupal-media> tags are processed.
*/
public function testOnlyDrupalMediaTagProcessed(): void {
$content = $this->createEmbedCode([
'data-entity-type' => 'media',
'data-entity-uuid' => $this->embeddedEntity->uuid(),
]);
$content = str_replace('drupal-media', 'drupal-entity', $content);
$filter_result = $this->processText($content, 'en', ['media_embed']);
// If input equals output, the filter didn't change anything.
$this->assertSame($content, $filter_result->getProcessedText());
}
/**
* Tests recursive rendering protection.
*/
public function testRecursionProtection(): void {
$text = $this->createEmbedCode([
'data-entity-type' => 'media',
'data-entity-uuid' => static::EMBEDDED_ENTITY_UUID,
]);
// Render and verify the presence of the embedded entity 20 times.
for ($i = 0; $i < 20; $i++) {
$this->applyFilter($text);
$this->assertCount(1, $this->cssSelect('div[data-media-embed-test-view-mode="default"]'));
}
// Render a 21st time, this is exceeding the recursion limit. The entity
// embed markup will be stripped.
$this->applyFilter($text);
$this->assertEmpty($this->getRawContent());
}
/**
* @covers \Drupal\filter\Plugin\Filter\FilterAlign
* @covers \Drupal\filter\Plugin\Filter\FilterCaption
* @dataProvider providerFilterIntegration
*/
public function testFilterIntegration(array $filter_ids, array $additional_attributes, $verification_selector, $expected_verification_success, array $expected_asset_libraries = [], $prefix = '', $suffix = ''): void {
$content = $this->createEmbedCode([
'data-entity-type' => 'media',
'data-entity-uuid' => static::EMBEDDED_ENTITY_UUID,
] + $additional_attributes);
$content = $prefix . $content . $suffix;
$result = $this->processText($content, 'en', $filter_ids);
$this->setRawContent($result->getProcessedText());
$this->assertCount($expected_verification_success ? 1 : 0, $this->cssSelect($verification_selector));
$this->assertCount(1, $this->cssSelect('div[data-media-embed-test-view-mode="default"]'));
$this->assertEqualsCanonicalizing([
'_media_test_embed_filter_access:media:1',
'_media_test_embed_filter_access:user:2',
'config:image.style.thumbnail',
'file:1',
'media:1',
'media_view',
'user:2',
], $result->getCacheTags());
$this->assertEqualsCanonicalizing(['timezone', 'user.permissions'], $result->getCacheContexts());
$this->assertSame(Cache::PERMANENT, $result->getCacheMaxAge());
$this->assertSame(['library'], array_keys($result->getAttachments()));
$this->assertSame($expected_asset_libraries, $result->getAttachments()['library']);
}
/**
* Data provider for testFilterIntegration().
*/
public static function providerFilterIntegration() {
$default_asset_libraries = ['media/filter.caption'];
$caption_additional_attributes = ['data-caption' => 'Yo.'];
$caption_verification_selector = 'figure > figcaption';
$caption_test_cases = [
'`data-caption`; only `media_embed` ⇒ caption absent' => [
['media_embed'],
$caption_additional_attributes,
$caption_verification_selector,
FALSE,
$default_asset_libraries,
],
'`data-caption`; `filter_caption` + `media_embed` ⇒ caption present' => [
['filter_caption', 'media_embed'],
$caption_additional_attributes,
$caption_verification_selector,
TRUE,
['filter/caption', 'media/filter.caption'],
],
'`<a>` + `data-caption`; `filter_caption` + `media_embed` ⇒ caption present, link preserved' => [
['filter_caption', 'media_embed'],
$caption_additional_attributes,
'figure > a[href="https://www.drupal.org"] + figcaption',
TRUE,
['filter/caption', 'media/filter.caption'],
'<a href="https://www.drupal.org">',
'</a>',
],
];
$align_additional_attributes = ['data-align' => 'center'];
$align_verification_selector = 'div[data-media-embed-test-view-mode].align-center';
$align_test_cases = [
'`data-align`; `media_embed` ⇒ alignment absent' => [
['media_embed'],
$align_additional_attributes,
$align_verification_selector,
FALSE,
$default_asset_libraries,
],
'`data-align`; `filter_align` + `media_embed` ⇒ alignment present' => [
['filter_align', 'media_embed'],
$align_additional_attributes,
$align_verification_selector,
TRUE,
$default_asset_libraries,
],
'`<a>` + `data-align`; `filter_align` + `media_embed` ⇒ alignment present, link preserved' => [
['filter_align', 'media_embed'],
$align_additional_attributes,
'a[href="https://www.drupal.org"] > div[data-media-embed-test-view-mode].align-center',
TRUE,
$default_asset_libraries,
'<a href="https://www.drupal.org">',
'</a>',
],
];
$caption_and_align_test_cases = [
'`data-caption` + `data-align`; `filter_align` + `filter_caption` + `media_embed` ⇒ aligned caption present' => [
['filter_align', 'filter_caption', 'media_embed'],
$align_additional_attributes + $caption_additional_attributes,
'figure.align-center > figcaption',
TRUE,
['filter/caption', 'media/filter.caption'],
],
'`<a>` + `data-caption` + `data-align`; `filter_align` + `filter_caption` + `media_embed` ⇒ aligned caption present, link preserved' => [
['filter_align', 'filter_caption', 'media_embed'],
$align_additional_attributes + $caption_additional_attributes,
'figure.align-center > a[href="https://www.drupal.org"] + figcaption',
TRUE,
['filter/caption', 'media/filter.caption'],
'<a href="https://www.drupal.org">',
'</a>',
],
];
return $caption_test_cases + $align_test_cases + $caption_and_align_test_cases;
}
}

View File

@ -0,0 +1,254 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Kernel;
use Drupal\Component\Utility\Html;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\Entity\Entity\EntityViewMode;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Render\RenderContext;
use Drupal\file\Entity\File;
use Drupal\filter\FilterPluginCollection;
use Drupal\filter\FilterProcessResult;
use Drupal\KernelTests\KernelTestBase;
use Drupal\media\Entity\Media;
use Drupal\Tests\media\Traits\MediaTypeCreationTrait;
use Drupal\Tests\TestFileCreationTrait;
use Drupal\Tests\user\Traits\UserCreationTrait;
/**
* Base class for Media Embed filter tests.
*/
abstract class MediaEmbedFilterTestBase extends KernelTestBase {
use MediaTypeCreationTrait;
use TestFileCreationTrait;
use UserCreationTrait {
createUser as drupalCreateUser;
createRole as drupalCreateRole;
}
/**
* The UUID to use for the embedded entity.
*
* @var string
*/
const EMBEDDED_ENTITY_UUID = 'e7a3e1fe-b69b-417e-8ee4-c80cb7640e63';
/**
* {@inheritdoc}
*/
protected static $modules = [
'field',
'file',
'filter',
'image',
'media',
'system',
'text',
'user',
];
/**
* The image file to use in tests.
*
* @var \Drupal\file\FileInterface
*/
protected $image;
/**
* The sample Media entity to embed.
*
* @var \Drupal\media\MediaInterface
*/
protected $embeddedEntity;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installSchema('file', ['file_usage']);
$this->installEntitySchema('file');
$this->installEntitySchema('media');
$this->installEntitySchema('user');
$this->installConfig('filter');
$this->installConfig('image');
$this->installConfig('media');
$this->installConfig('system');
// Create a user with required permissions. Ensure that we don't use user 1
// because that user is treated in special ways by access control handlers.
$this->drupalCreateUser([]);
$user = $this->drupalCreateUser([
'access content',
'view media',
]);
$this->container->get('current_user')->setAccount($user);
$this->image = File::create([
'uri' => $this->getTestFiles('image')[0]->uri,
'uid' => 2,
]);
$this->image->setPermanent();
$this->image->save();
// Create a sample media entity to be embedded.
$media_type = $this->createMediaType('image', ['id' => 'image']);
EntityViewMode::create([
'id' => 'media.foobar',
'targetEntityType' => 'media',
'status' => TRUE,
'enabled' => TRUE,
'label' => $this->randomMachineName(),
])->save();
EntityViewDisplay::create([
'targetEntityType' => 'media',
'bundle' => $media_type->id(),
'mode' => 'foobar',
'status' => TRUE,
])->removeComponent('thumbnail')
->removeComponent('created')
->removeComponent('uid')
->setComponent('field_media_image', [
'label' => 'visually_hidden',
'type' => 'image',
'settings' => [
'image_style' => 'medium',
'image_link' => 'file',
],
'third_party_settings' => [],
'weight' => 1,
'region' => 'content',
])
->save();
$media = Media::create([
'uuid' => static::EMBEDDED_ENTITY_UUID,
'bundle' => 'image',
'name' => 'Screaming hairy armadillo',
'field_media_image' => [
[
'target_id' => $this->image->id(),
'alt' => 'default alt',
'title' => 'default title',
],
],
])->setOwner($user);
$media->save();
$this->embeddedEntity = $media;
}
/**
* Gets an embed code with given attributes.
*
* @param array $attributes
* The attributes to add.
*
* @return string
* A string containing a drupal-media DOM element.
*
* @see assertEntityEmbedFilterHasRun()
*/
protected function createEmbedCode(array $attributes) {
$dom = Html::load('<drupal-media>This placeholder should not be rendered.</drupal-media>');
$xpath = new \DOMXPath($dom);
$drupal_entity = $xpath->query('//drupal-media')[0];
foreach ($attributes as $attribute => $value) {
$drupal_entity->setAttribute($attribute, $value);
}
return Html::serialize($dom);
}
/**
* Applies the `media_embed` filter to text, pipes to raw content.
*
* @param string $text
* The text string to be filtered.
* @param string $langcode
* The language code of the text to be filtered.
*
* @return \Drupal\filter\FilterProcessResult
* The filtered text, wrapped in a FilterProcessResult object, and possibly
* with associated assets, cacheability metadata and placeholders.
*
* @see \Drupal\Tests\media\Kernel\MediaEmbedFilterTestBase::createEmbedCode()
* @see \Drupal\KernelTests\AssertContentTrait::setRawContent()
*/
protected function applyFilter($text, $langcode = 'en') {
$this->assertStringContainsString('<drupal-media', $text);
$this->assertStringContainsString('This placeholder should not be rendered.', $text);
$filter_result = $this->processText($text, $langcode);
$output = $filter_result->getProcessedText();
$this->assertStringNotContainsString('<drupal-media', $output);
$this->assertStringNotContainsString('This placeholder should not be rendered.', $output);
$this->setRawContent($output);
return $filter_result;
}
/**
* Assert that the SimpleXMLElement object has the given attributes.
*
* @param \SimpleXMLElement $element
* The SimpleXMLElement object to check.
* @param array $expected_attributes
* An array of expected attributes.
*/
protected function assertHasAttributes(\SimpleXMLElement $element, array $expected_attributes) {
foreach ($expected_attributes as $attribute => $value) {
if ($value === NULL) {
$this->assertNull($element[$attribute]);
}
else {
$this->assertSame((string) $value, (string) $element[$attribute]);
}
}
}
/**
* Processes text through the provided filters.
*
* @param string $text
* The text string to be filtered.
* @param string $langcode
* The language code of the text to be filtered.
* @param string[] $filter_ids
* (optional) The filter plugin IDs to apply to the given text, in the order
* they are being requested to be executed.
*
* @return \Drupal\filter\FilterProcessResult
* The filtered text, wrapped in a FilterProcessResult object, and possibly
* with associated assets, cacheability metadata and placeholders.
*
* @see \Drupal\filter\Element\ProcessedText::preRenderText()
*/
protected function processText($text, $langcode = LanguageInterface::LANGCODE_NOT_SPECIFIED, array $filter_ids = ['media_embed']) {
$manager = $this->container->get('plugin.manager.filter');
$bag = new FilterPluginCollection($manager, []);
$filters = [];
foreach ($filter_ids as $filter_id) {
$filters[] = $bag->get($filter_id);
}
$render_context = new RenderContext();
/** @var \Drupal\filter\FilterProcessResult $filter_result */
$filter_result = $this->container->get('renderer')->executeInRenderContext($render_context, function () use ($text, $filters, $langcode) {
$metadata = new BubbleableMetadata();
foreach ($filters as $filter) {
/** @var \Drupal\filter\FilterProcessResult $result */
$result = $filter->process($text, $langcode);
$metadata = $metadata->merge($result);
$text = $result->getProcessedText();
}
return (new FilterProcessResult($text))->merge($metadata);
});
if (!$render_context->isEmpty()) {
$filter_result = $filter_result->merge($render_context->pop());
}
return $filter_result;
}
}

View File

@ -0,0 +1,90 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Kernel;
use Drupal\language\Entity\ConfigurableLanguage;
/**
* Tests that media embeds are translated based on text (host entity) language.
*
* @coversDefaultClass \Drupal\media\Plugin\Filter\MediaEmbed
* @group media
*/
class MediaEmbedFilterTranslationTest extends MediaEmbedFilterTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'language',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
ConfigurableLanguage::createFromLangcode('pt-br')->save();
// Reload the entity to ensure it is aware of the newly created language.
$this->embeddedEntity = $this->container->get('entity_type.manager')
->getStorage('media')
->load($this->embeddedEntity->id());
$this->embeddedEntity->addTranslation('pt-br')
->set('field_media_image', [
'target_id' => $this->image->id(),
'alt' => 'pt-br alt',
'title' => 'pt-br title',
])->save();
}
/**
* Tests that the expected embedded media entity translation is selected.
*
* @dataProvider providerTranslationSituations
*/
public function testTranslationSelection($text_langcode, $expected_title_langcode): void {
$text = $this->createEmbedCode([
'data-entity-type' => 'media',
'data-entity-uuid' => static::EMBEDDED_ENTITY_UUID,
]);
$result = $this->processText($text, $text_langcode, ['media_embed']);
$this->setRawContent($result->getProcessedText());
$this->assertSame(
$this->embeddedEntity->getTranslation($expected_title_langcode)->field_media_image->alt,
(string) $this->cssSelect('img')[0]->attributes()['alt']
);
// Verify that the filtered text does not vary by translation-related cache
// contexts: a particular translation of the embedded entity is selected
// based on the host entity's language, which should require a cache context
// to be associated. (The host entity's language may itself be selected
// based on the request context, but that is of no concern to this filter.)
$this->assertEqualsCanonicalizing($result->getCacheContexts(), ['timezone', 'user.permissions']);
}
/**
* Data provider for testTranslationSelection().
*/
public static function providerTranslationSituations() {
$embedded_entity_translation_languages = ['en', 'pt-br'];
foreach (['en', 'pt-br', 'nl'] as $text_langcode) {
// The text language (which is set to the host entity's language) must be
// respected in selecting a translation. If that translation does not
// exist, it falls back to the default translation of the embedded entity.
$match_or_fallback_langcode = in_array($text_langcode, $embedded_entity_translation_languages)
? $text_langcode
: 'en';
yield "text_langcode=$text_langcode$match_or_fallback_langcode" => [
$text_langcode,
$match_or_fallback_langcode,
];
}
}
}

View File

@ -0,0 +1,120 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Kernel;
use Drupal\file\Entity\File;
use Drupal\KernelTests\KernelTestBase;
use Drupal\media\Entity\Media;
use Drupal\media\MediaTypeInterface;
use Drupal\Tests\media\Traits\MediaTypeCreationTrait;
use Drupal\user\Entity\User;
use org\bovigo\vfs\vfsStream;
/**
* Base class for Media kernel tests.
*/
abstract class MediaKernelTestBase extends KernelTestBase {
use MediaTypeCreationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'media',
'media_test_source',
'image',
'user',
'field',
'system',
'file',
];
/**
* The test media type.
*
* @var \Drupal\media\MediaTypeInterface
*/
protected $testMediaType;
/**
* The test media type with constraints.
*
* @var \Drupal\media\MediaTypeInterface
*/
protected $testConstraintsMediaType;
/**
* A user.
*
* @var \Drupal\user\UserInterface
*/
protected $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', 'image', 'file', 'media']);
// Create a test media type.
$this->testMediaType = $this->createMediaType('test');
// Create a test media type with constraints.
$this->testConstraintsMediaType = $this->createMediaType('test_constraints');
$this->user = User::create([
'name' => 'username',
'status' => 1,
]);
$this->user->save();
$this->container->get('current_user')->setAccount($this->user);
}
/**
* Helper to generate a media item.
*
* @param string $filename
* String filename with extension.
* @param \Drupal\media\MediaTypeInterface $media_type
* The media type.
*
* @return \Drupal\media\Entity\Media
* A media item.
*/
protected function generateMedia($filename, MediaTypeInterface $media_type) {
vfsStream::setup('drupal_root');
vfsStream::create([
'sites' => [
'default' => [
'files' => [
$filename => str_repeat('a', 3000),
],
],
],
]);
$file = File::create([
'uri' => 'vfs://drupal_root/sites/default/files/' . $filename,
'uid' => $this->user->id(),
]);
$file->setPermanent();
$file->save();
return Media::create([
'bundle' => $media_type->id(),
'name' => 'Mr. Jones',
'field_media_file' => [
'target_id' => $file->id(),
],
]);
}
}

View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Kernel;
use Drupal\media\Entity\Media;
/**
* Tests link relationships for media items.
*
* @group media
*/
class MediaLinkRelationsTest extends MediaKernelTestBase {
/**
* Tests that all link relationships for Media exist.
*/
public function testExistLinkRelationships(): void {
/** @var \Drupal\Core\Http\LinkRelationTypeManager $link_relation_type_manager */
$link_relation_type_manager = $this->container->get('plugin.manager.link_relation_type');
$media = Media::create(['bundle' => $this->testMediaType->id()]);
$media->save();
foreach ($media->uriRelationships() as $relation_name) {
$this->assertTrue($link_relation_type_manager->hasDefinition($relation_name), "Link relationship '{$relation_name}' for a media item");
}
}
}

View File

@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\media\Plugin\media\Source\File;
use Drupal\Tests\media\Traits\MediaTypeCreationTrait;
use Symfony\Component\Validator\ConstraintViolationListInterface;
/**
* @coversDefaultClass \Drupal\media\Plugin\Validation\Constraint\MediaMappingsConstraintValidator
*
* @group media
*/
class MediaMappingsConstraintValidatorTest extends KernelTestBase {
use MediaTypeCreationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['field', 'file', 'image', 'media', 'user'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('file');
$this->installEntitySchema('media');
$this->installEntitySchema('user');
}
/**
* @covers ::validate
*/
public function testMediaMappingSource(): void {
$media_type = $this->createMediaType('image', [
'id' => 'test',
]);
$source_field_name = $media_type->getSource()
->getSourceFieldDefinition($media_type)
->getName();
$field_map = $media_type->getFieldMap();
$field_map[File::METADATA_ATTRIBUTE_MIME] = $source_field_name;
$media_type->setFieldMap($field_map);
$media_type->save();
$typed_data = $this->container->get('typed_data_manager');
$definition = $typed_data->createDataDefinition('entity:media_type');
$violations = $typed_data->create($definition, $media_type)->validate();
assert($violations instanceof ConstraintViolationListInterface);
$this->assertCount(1, $violations);
$this->assertEquals('It is not possible to map the source field ' . $source_field_name . ' of a media type.', $violations[0]->getMessage());
}
}

View File

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Kernel;
/**
* Tests the file media source.
*
* @group media
*/
class MediaSourceFileTest extends MediaKernelTestBase {
/**
* Tests the file extension constraint.
*/
public function testFileExtensionConstraint(): void {
$mediaType = $this->createMediaType('file');
// Create a random file that should fail.
$media = $this->generateMedia('test.patch', $mediaType);
$result = $media->validate();
$this->assertCount(1, $result);
$this->assertSame('field_media_file.0', $result->get(0)->getPropertyPath());
$this->assertStringContainsString('Only files with the following extensions are allowed:', (string) $result->get(0)->getMessage());
// Create a random file that should pass.
$media = $this->generateMedia('test.txt', $mediaType);
$result = $media->validate();
$this->assertCount(0, $result);
}
/**
* Tests a media file can be deleted.
*/
public function testFileDeletion(): void {
$mediaType = $this->createMediaType('file');
$media = $this->generateMedia('test.txt', $mediaType);
$media->save();
$source_field_name = $mediaType->getSource()
->getSourceFieldDefinition($mediaType)
->getName();
/** @var \Drupal\file\FileInterface $file */
$file = $media->get($source_field_name)->entity;
$file->delete();
$this->assertEmpty($this->container->get('entity_type.manager')->getStorage('file')->loadByProperties(['filename' => 'test.txt']));
}
}

View File

@ -0,0 +1,687 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Kernel;
use Drupal\Core\Form\FormState;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\media\Entity\Media;
use Drupal\media\Entity\MediaType;
// cspell:ignore sisko
/**
* Tests media source plugins related logic.
*
* @group media
*/
class MediaSourceTest extends MediaKernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['field_ui'];
/**
* Tests that metadata is correctly mapped irrespective of how media is saved.
*/
public function testSave(): void {
$field_storage = FieldStorageConfig::create([
'entity_type' => 'media',
'field_name' => 'field_to_map_to',
'type' => 'string',
]);
$field_storage->save();
FieldConfig::create([
'field_storage' => $field_storage,
'bundle' => $this->testMediaType->id(),
'label' => 'Field to map to',
])->save();
// Set an arbitrary metadata value to be mapped.
$this->container->get('state')
->set('media_source_test_attributes', [
'attribute_to_map' => [
'title' => 'Attribute to map',
'value' => 'Snowball',
],
'thumbnail_uri' => [
'value' => 'public://TheSisko.png',
],
]);
$this->testMediaType->setFieldMap([
'attribute_to_map' => 'field_to_map_to',
])->save();
/** @var \Drupal\Core\Entity\EntityStorageInterface $storage */
$storage = $this->container->get('entity_type.manager')
->getStorage('media');
/** @var \Drupal\media\MediaInterface $a */
$a = $storage->create([
'bundle' => $this->testMediaType->id(),
]);
/** @var \Drupal\media\MediaInterface $b */
$b = $storage->create([
'bundle' => $this->testMediaType->id(),
]);
// Set a random source value on both items.
$a->set($a->getSource()->getSourceFieldDefinition($a->bundle->entity)->getName(), $this->randomString());
$b->set($b->getSource()->getSourceFieldDefinition($b->bundle->entity)->getName(), $this->randomString());
$a->save();
$storage->save($b);
// Assert that the default name was mapped into the name field for both
// media items.
$this->assertFalse($a->get('name')->isEmpty());
$this->assertFalse($b->get('name')->isEmpty());
// Assert that arbitrary metadata was mapped correctly.
$this->assertFalse($a->get('field_to_map_to')->isEmpty());
$this->assertFalse($b->get('field_to_map_to')->isEmpty());
// Assert that the thumbnail was mapped correctly from the source.
$this->assertSame('public://TheSisko.png', $a->thumbnail->entity->getFileUri());
$this->assertSame('public://TheSisko.png', $b->thumbnail->entity->getFileUri());
}
/**
* Tests default media name functionality.
*/
public function testDefaultName(): void {
// Make sure that the default name is set if not provided by the user.
/** @var \Drupal\media\MediaInterface $media */
$media = Media::create(['bundle' => $this->testMediaType->id()]);
$media_source = $media->getSource();
$this->assertSame('default_name', $media_source->getPluginDefinition()['default_name_metadata_attribute'], 'Default metadata attribute is not used for the default name.');
$this->assertSame('media:' . $media->bundle() . ':' . $media->uuid(), $media_source->getMetadata($media, 'default_name'), 'Value of the default name metadata attribute does not look correct.');
$this->assertSame('media:' . $media->bundle() . ':' . $media->uuid(), $media->getName(), 'Default name was not used correctly by getName().');
$this->assertSame($media->getName(), $media->label(), 'Default name and label are not the same.');
$media->save();
$this->assertSame('media:' . $media->bundle() . ':' . $media->uuid(), $media->getName(), 'Default name was not saved correctly.');
$this->assertSame($media->getName(), $media->label(), 'The label changed during save.');
// Make sure that the user-supplied name is used.
/** @var \Drupal\media\MediaInterface $media */
$name = 'User-supplied name';
$media = Media::create([
'bundle' => $this->testMediaType->id(),
'name' => $name,
]);
$media_source = $media->getSource();
$this->assertSame('default_name', $media_source->getPluginDefinition()['default_name_metadata_attribute'], 'Default metadata attribute is not used for the default name.');
$this->assertSame('media:' . $media->bundle() . ':' . $media->uuid(), $media_source->getMetadata($media, 'default_name'), 'Value of the default name metadata attribute does not look correct.');
$media->save();
$this->assertSame($name, $media->getName(), 'User-supplied name was not set correctly.');
$this->assertSame($media->getName(), $media->label(), 'The user-supplied name does not match the label.');
// Change the default name attribute and see if it is used to set the name.
$name = 'Old Major';
\Drupal::state()
->set('media_source_test_attributes', ['alternative_name' => ['title' => 'Alternative name', 'value' => $name]]);
\Drupal::state()
->set('media_source_test_definition', ['default_name_metadata_attribute' => 'alternative_name']);
/** @var \Drupal\media\MediaInterface $media */
$media = Media::create(['bundle' => $this->testMediaType->id()]);
$media_source = $media->getSource();
$this->assertSame('alternative_name', $media_source->getPluginDefinition()['default_name_metadata_attribute'], 'Correct metadata attribute is not used for the default name.');
$this->assertSame($name, $media_source->getMetadata($media, 'alternative_name'), 'Value of the default name metadata attribute does not look correct.');
$media->save();
$this->assertSame($name, $media->getName(), 'Default name was not set correctly.');
$this->assertSame($media->getName(), $media->label(), 'The default name does not match the label.');
}
/**
* Tests metadata mapping functionality.
*/
public function testMetadataMapping(): void {
$field_name = 'field_to_map_to';
$attribute_name = 'attribute_to_map';
$storage = FieldStorageConfig::create([
'entity_type' => 'media',
'field_name' => $field_name,
'type' => 'string',
]);
$storage->save();
FieldConfig::create([
'field_storage' => $storage,
'bundle' => $this->testMediaType->id(),
'label' => 'Field to map to',
])->save();
// Save the entity without defining the metadata mapping and check that the
// field stays empty.
/** @var \Drupal\media\MediaInterface $media */
$media = Media::create([
'bundle' => $this->testMediaType->id(),
'field_media_test' => 'some_value',
]);
$media->save();
$this->assertEmpty($media->get($field_name)->value, 'Field stayed empty.');
// Make sure that source plugin returns NULL for non-existing fields.
$this->testMediaType->setFieldMap(['not_here_at_all' => $field_name])->save();
$media = Media::create([
'bundle' => $this->testMediaType->id(),
'field_media_test' => 'some_value',
]);
$media_source = $media->getSource();
$this->assertNull($media_source->getMetadata($media, 'not_here_at_all'), 'NULL is not returned if asking for a value of non-existing metadata.');
$media->save();
$this->assertTrue($media->get($field_name)->isEmpty(), 'Non-existing metadata attribute was wrongly mapped to the field.');
// Define mapping and make sure that the value was stored in the field.
\Drupal::state()->set('media_source_test_attributes', [
$attribute_name => ['title' => 'Attribute to map', 'value' => 'Snowball'],
]);
$this->testMediaType->setFieldMap([$attribute_name => $field_name])->save();
$media = Media::create([
'bundle' => $this->testMediaType->id(),
'field_media_test' => 'some_value',
]);
$media_source = $media->getSource();
$this->assertSame('Snowball', $media_source->getMetadata($media, $attribute_name), 'Value of the metadata attribute is not correct.');
$media->save();
$this->assertSame('Snowball', $media->get($field_name)->value, 'Metadata attribute was not mapped to the field.');
// Change the metadata attribute value and re-save the entity. Field value
// should stay the same.
\Drupal::state()->set('media_source_test_attributes', [
$attribute_name => ['title' => 'Attribute to map', 'value' => 'Pinkeye'],
]);
$this->assertSame('Pinkeye', $media_source->getMetadata($media, $attribute_name), 'Value of the metadata attribute is not correct.');
$media->save();
$this->assertSame('Snowball', $media->get($field_name)->value, 'Metadata attribute was not mapped to the field.');
// Now change the value of the source field and make sure that the mapped
// values update too.
$this->assertSame('Pinkeye', $media_source->getMetadata($media, $attribute_name), 'Value of the metadata attribute is not correct.');
$media->set('field_media_test', 'some_new_value');
$media->save();
$this->assertSame('Pinkeye', $media->get($field_name)->value, 'Metadata attribute was not mapped to the field.');
// Remove the value of the mapped field and make sure that it is re-mapped
// on save.
\Drupal::state()->set('media_source_test_attributes', [
$attribute_name => ['title' => 'Attribute to map', 'value' => 'Snowball'],
]);
$media->{$field_name}->value = NULL;
$this->assertSame('Snowball', $media_source->getMetadata($media, $attribute_name), 'Value of the metadata attribute is not correct.');
$media->save();
$this->assertSame('Snowball', $media->get($field_name)->value, 'Metadata attribute was not mapped to the field.');
}
/**
* Tests the getSourceFieldValue() method.
*/
public function testGetSourceFieldValue(): void {
/** @var \Drupal\media\MediaInterface $media */
$media = Media::create([
'bundle' => $this->testMediaType->id(),
'field_media_test' => 'some_value',
]);
$media->save();
$media_source = $media->getSource();
$this->assertSame('some_value', $media_source->getSourceFieldValue($media));
// Test that NULL is returned if there is no value in the source field.
$media->set('field_media_test', NULL)->save();
$this->assertNull($media_source->getSourceFieldValue($media));
}
/**
* Tests the thumbnail functionality.
*/
public function testThumbnail(): void {
file_put_contents('public://thumbnail1.jpg', '');
file_put_contents('public://thumbnail2.jpg', '');
// Save a media item and make sure thumbnail was added.
\Drupal::state()->set('media_source_test_attributes', [
'thumbnail_uri' => ['value' => 'public://thumbnail1.jpg'],
]);
/** @var \Drupal\media\MediaInterface $media */
$media = Media::create([
'bundle' => $this->testMediaType->id(),
'name' => 'Mr. Jones',
'field_media_test' => 'some_value',
]);
$media_source = $media->getSource();
$this->assertSame('public://thumbnail1.jpg', $media_source->getMetadata($media, 'thumbnail_uri'), 'Value of the thumbnail metadata attribute is not correct.');
$media->save();
$this->assertSame('public://thumbnail1.jpg', $media->thumbnail->entity->getFileUri(), 'Thumbnail was not added to the media item.');
// We expect the title not to be present on the Thumbnail.
$this->assertEmpty($media->thumbnail->title);
$this->assertSame('', $media->thumbnail->alt);
// Now change the metadata attribute and make sure that the thumbnail stays
// the same.
\Drupal::state()->set('media_source_test_attributes', [
'thumbnail_uri' => ['value' => 'public://thumbnail2.jpg'],
]);
$this->assertSame('public://thumbnail2.jpg', $media_source->getMetadata($media, 'thumbnail_uri'), 'Value of the thumbnail metadata attribute is not correct.');
$media->save();
$this->assertSame('public://thumbnail1.jpg', $media->thumbnail->entity->getFileUri(), 'Thumbnail was not preserved.');
$this->assertEmpty($media->thumbnail->title);
$this->assertSame('', $media->thumbnail->alt);
// Remove the thumbnail and make sure that it is auto-updated on save.
$media->thumbnail->target_id = NULL;
$this->assertSame('public://thumbnail2.jpg', $media_source->getMetadata($media, 'thumbnail_uri'), 'Value of the thumbnail metadata attribute is not correct.');
$media->save();
$this->assertSame('public://thumbnail2.jpg', $media->thumbnail->entity->getFileUri(), 'New thumbnail was not added to the media item.');
$this->assertEmpty($media->thumbnail->title);
$this->assertSame('', $media->thumbnail->alt);
// Change the metadata attribute again, change the source field value too
// and make sure that the thumbnail updates.
\Drupal::state()->set('media_source_test_attributes', [
'thumbnail_uri' => ['value' => 'public://thumbnail1.jpg'],
]);
$media->field_media_test->value = 'some_new_value';
$this->assertSame('public://thumbnail1.jpg', $media_source->getMetadata($media, 'thumbnail_uri'), 'Value of the thumbnail metadata attribute is not correct.');
$media->save();
$this->assertSame('public://thumbnail1.jpg', $media->thumbnail->entity->getFileUri(), 'New thumbnail was not added to the media item.');
$this->assertEmpty($media->thumbnail->title);
$this->assertSame('', $media->thumbnail->alt);
// Change the thumbnail metadata attribute and make sure that the thumbnail
// is set correctly.
\Drupal::state()->set('media_source_test_attributes', [
'thumbnail_uri' => ['value' => 'public://thumbnail1.jpg'],
'alternative_thumbnail_uri' => ['value' => 'public://thumbnail2.jpg'],
]);
\Drupal::state()->set('media_source_test_definition', ['thumbnail_uri_metadata_attribute' => 'alternative_thumbnail_uri']);
$media = Media::create([
'bundle' => $this->testMediaType->id(),
'name' => 'Mr. Jones',
'field_media_test' => 'some_value',
]);
$media_source = $media->getSource();
$this->assertSame('public://thumbnail1.jpg', $media_source->getMetadata($media, 'thumbnail_uri'), 'Value of the metadata attribute is not correct.');
$this->assertSame('public://thumbnail2.jpg', $media_source->getMetadata($media, 'alternative_thumbnail_uri'), 'Value of the thumbnail metadata attribute is not correct.');
$media->save();
$this->assertSame('public://thumbnail2.jpg', $media->thumbnail->entity->getFileUri(), 'Correct metadata attribute was not used for the thumbnail.');
$this->assertEmpty($media->thumbnail->title);
$this->assertSame('', $media->thumbnail->alt);
// Set the width and height metadata attributes and make sure they're used
// for the thumbnail.
\Drupal::state()->set('media_source_test_definition', [
'thumbnail_width_metadata_attribute' => 'width',
'thumbnail_height_metadata_attribute' => 'height',
]);
\Drupal::state()->set('media_source_test_attributes', [
'width' => ['value' => 1024],
'height' => ['value' => 768],
]);
$media = Media::create([
'bundle' => $this->testMediaType->id(),
'name' => 'Are you looking at me?',
'field_media_test' => 'some_value',
]);
$media->save();
$this->assertSame(1024, $media->thumbnail->width);
$this->assertSame(768, $media->thumbnail->height);
// Enable queued thumbnails and make sure that the entity gets the default
// thumbnail initially.
\Drupal::state()->set('media_source_test_definition', []);
\Drupal::state()->set('media_source_test_attributes', [
'thumbnail_uri' => ['value' => 'public://thumbnail1.jpg'],
]);
$this->testMediaType->setQueueThumbnailDownloadsStatus(TRUE)->save();
$media = Media::create([
'bundle' => $this->testMediaType->id(),
'name' => 'Mr. Jones',
'field_media_test' => 'some_value',
]);
$this->assertSame('public://thumbnail1.jpg', $media->getSource()->getMetadata($media, 'thumbnail_uri'), 'Value of the metadata attribute is not correct.');
$media->save();
$this->assertSame('public://media-icons/generic/generic.png', $media->thumbnail->entity->getFileUri(), 'Default thumbnail was not set initially.');
$this->assertEmpty($media->thumbnail->title);
$this->assertSame('', $media->thumbnail->alt);
// Process the queue item and make sure that the thumbnail was updated too.
$queue_name = 'media_entity_thumbnail';
/** @var \Drupal\Core\Queue\QueueWorkerInterface $queue_worker */
$queue_worker = \Drupal::service('plugin.manager.queue_worker')->createInstance($queue_name);
$queue = \Drupal::queue($queue_name);
$this->assertSame(1, $queue->numberOfItems(), 'Item was not added to the queue.');
$item = $queue->claimItem();
$this->assertSame($media->id(), $item->data['id'], 'Queue item that was created does not belong to the correct entity.');
$queue_worker->processItem($item->data);
$queue->deleteItem($item);
$this->assertSame(0, $queue->numberOfItems(), 'Item was not removed from the queue.');
$media = Media::load($media->id());
$this->assertSame('public://thumbnail1.jpg', $media->thumbnail->entity->getFileUri(), 'Thumbnail was not updated by the queue.');
$this->assertEmpty($media->thumbnail->title);
$this->assertSame('', $media->thumbnail->alt);
// Set the alt metadata attribute and make sure it's used for the thumbnail.
\Drupal::state()->set('media_source_test_definition', [
'thumbnail_alt_metadata_attribute' => 'alt',
]);
\Drupal::state()->set('media_source_test_attributes', [
'alt' => ['value' => 'This will be alt.'],
]);
$media = Media::create([
'bundle' => $this->testMediaType->id(),
'name' => 'Boxer',
'field_media_test' => 'some_value',
]);
$media->save();
$this->assertSame('Boxer', $media->getName(), 'Correct name was not set on the media item.');
$this->assertEmpty($media->thumbnail->title);
$this->assertSame('This will be alt.', $media->thumbnail->alt);
}
/**
* Tests the media item constraints functionality.
*/
public function testConstraints(): void {
// Test entity constraints.
\Drupal::state()->set('media_source_test_entity_constraints', [
'MediaTestConstraint' => [],
]);
// Create a media item media that uses a source plugin with constraints and
// make sure the constraints works as expected when validating.
/** @var \Drupal\media\MediaInterface $media */
$media = Media::create([
'bundle' => $this->testConstraintsMediaType->id(),
'name' => 'I do not like Drupal',
'field_media_test_constraints' => 'Not checked',
]);
// Validate the entity and make sure violation is reported.
/** @var \Drupal\Core\Entity\EntityConstraintViolationListInterface $violations */
$violations = $media->validate();
$this->assertCount(1, $violations, 'Expected number of validations not found.');
$this->assertEquals('Inappropriate text.', $violations->get(0)->getMessage(), 'Incorrect constraint validation message found.');
// Fix the violation and make sure it is not reported anymore.
$media->setName('I love Drupal!');
$violations = $media->validate();
$this->assertCount(0, $violations, 'Expected number of validations not found.');
// Save and make sure it succeeded.
$this->assertEmpty($media->id(), 'Entity ID was found.');
$media->save();
$this->assertNotEmpty($media->id(), 'Entity ID was not found.');
$this->assertSame($media->getName(), 'I love Drupal!');
// Test source field constraints.
\Drupal::state()->set('media_source_test_field_constraints', [
'MediaTestConstraint' => [],
]);
\Drupal::state()->set('media_source_test_entity_constraints', []);
// Create media that uses source with constraints and make sure it can't
// be saved without validating them.
/** @var \Drupal\media\MediaInterface $media */
$media = Media::create([
'bundle' => $this->testConstraintsMediaType->id(),
'name' => 'Not checked',
'field_media_test_constraints' => 'I do not like Drupal',
]);
// Validate the entity and make sure violation is reported.
/** @var \Drupal\Core\Entity\EntityConstraintViolationListInterface $violations */
$violations = $media->validate();
$this->assertCount(1, $violations, 'Expected number of validations not found.');
$this->assertEquals('Inappropriate text.', $violations->get(0)->getMessage(), 'Incorrect constraint validation message found.');
// Fix the violation and make sure it is not reported anymore.
$media->set('field_media_test_constraints', 'I love Drupal!');
$violations = $media->validate();
$this->assertCount(0, $violations, 'Expected number of validations not found.');
// Save and make sure it succeeded.
$this->assertEmpty($media->id(), 'Entity ID was found.');
$media->save();
$this->assertNotEmpty($media->id(), 'Entity ID was not found.');
}
/**
* Tests logic related to the automated source field creation.
*/
public function testSourceFieldCreation(): void {
/** @var \Drupal\media\MediaTypeInterface $type */
$type = MediaType::create([
'id' => 'test_type',
'label' => 'Test type',
'source' => 'test',
]);
/** @var \Drupal\field\Entity\FieldConfig $field */
$field = $type->getSource()->createSourceField($type);
/** @var \Drupal\field\Entity\FieldStorageConfig $field_storage */
$field_storage = $field->getFieldStorageDefinition();
// Test field storage.
$this->assertTrue($field_storage->isNew(), 'Field storage is saved automatically.');
$this->assertFalse($field_storage->isLocked(), 'Field storage is not locked.');
$this->assertSame('string', $field_storage->getType(), 'Field is not of correct type.');
$this->assertSame('field_media_test_1', $field_storage->getName(), 'Incorrect field name is used.');
$this->assertSame('media', $field_storage->getTargetEntityTypeId(), 'Field is not targeting media entities.');
// Test field.
$this->assertTrue($field->isNew(), 'Field is saved automatically.');
$this->assertSame('field_media_test_1', $field->getName(), 'Incorrect field name is used.');
$this->assertSame('string', $field->getType(), 'Field is of incorrect type.');
$this->assertTrue($field->isRequired(), 'Field is not required.');
$this->assertEquals('Test source', $field->label(), 'Incorrect label is used.');
$this->assertSame('test_type', $field->getTargetBundle(), 'Field is not targeting correct bundle.');
// Fields should be automatically saved only when creating the media type
// using the media type creation form. Make sure that they are not saved
// when creating a media type programmatically.
// Drupal\Tests\media\FunctionalJavascript\MediaTypeCreationTest is testing
// form part of the functionality.
$type->save();
$storage = FieldStorageConfig::load('media.field_media_test_1');
$this->assertNull($storage, 'Field storage was not saved.');
$field = FieldConfig::load('media.test_type.field_media_test_1');
$this->assertNull($field, 'Field storage was not saved.');
// Test the plugin with a different default source field type.
$type = MediaType::create([
'id' => 'test_constraints_type',
'label' => 'Test type with constraints',
'source' => 'test_constraints',
]);
$field = $type->getSource()->createSourceField($type);
$field_storage = $field->getFieldStorageDefinition();
// Test field storage.
$this->assertTrue($field_storage->isNew(), 'Field storage is saved automatically.');
$this->assertFalse($field_storage->isLocked(), 'Field storage is not locked.');
$this->assertSame('string_long', $field_storage->getType(), 'Field is of incorrect type.');
$this->assertSame('field_media_test_constraints_1', $field_storage->getName(), 'Incorrect field name is used.');
$this->assertSame('media', $field_storage->getTargetEntityTypeId(), 'Field is not targeting media entities.');
// Test field.
$this->assertTrue($field->isNew(), 'Field is saved automatically.');
$this->assertSame('field_media_test_constraints_1', $field->getName(), 'Incorrect field name is used.');
$this->assertSame('string_long', $field->getType(), 'Field is of incorrect type.');
$this->assertTrue($field->isRequired(), 'Field is not required.');
$this->assertEquals('Test source with constraints', $field->label(), 'Incorrect label is used.');
$this->assertSame('test_constraints_type', $field->getTargetBundle(), 'Field is not targeting correct bundle.');
// Test a source with a long machine name.
$type = MediaType::create([
'id' => 'test_type_fail',
'label' => 'Test type - Fail',
'source' => 'test_source_with_a_really_long_name',
]);
$type->save();
/** @var \Drupal\field\Entity\FieldConfig $field */
$field = $type->getSource()->createSourceField($type);
/** @var \Drupal\field\Entity\FieldStorageConfig $field_storage */
$field_storage = $field->getFieldStorageDefinition();
$field_storage->save();
// Field configuration depends on the field storage, which must be saved first.
$field->save();
// Test long field name is truncated.
$this->assertSame('field_media_test_source_with_a_r', $field_storage->getName(), 'Incorrect field name is used.');
$type = MediaType::create([
'id' => 'test_type_fail_2',
'label' => 'Test type - Fail 2',
'source' => 'test_source_with_a_really_long_name',
]);
$type->save();
/** @var \Drupal\field\Entity\FieldConfig $field */
$field = $type->getSource()->createSourceField($type);
/** @var \Drupal\field\Entity\FieldStorageConfig $field_storage */
$field_storage = $field->getFieldStorageDefinition();
$field_storage->save();
// Field configuration depends on the field storage, which must be saved first.
$field->save();
// Test long field name is truncated.
$this->assertSame('field_media_test_source_with_a_1', $field_storage->getName(), 'Incorrect field name is used.');
// Test that new source fields respect the configured field prefix, no
// prefix at all if that's what's configured.
$this->installConfig('field_ui');
$this->config('field_ui.settings')
->set('field_prefix', 'prefix_')
->save();
$type = MediaType::create([
'id' => $this->randomMachineName(),
'label' => $this->randomString(),
'source' => 'test',
]);
$this->assertSame('prefix_media_test', $type->getSource()->createSourceField($type)->getName());
$this->config('field_ui.settings')
->set('field_prefix', '')
->save();
$this->assertSame('media_test', $type->getSource()->createSourceField($type)->getName());
}
/**
* Tests configuration form submit handler on the base media source plugin.
*/
public function testSourceConfigurationSubmit(): void {
/** @var \Drupal\media\MediaSourceManager $manager */
$manager = $this->container->get('plugin.manager.media.source');
$form = [];
$form_state = new FormState();
$form_state->setValues(['test_config_value' => 'Somewhere over the rainbow.']);
/** @var \Drupal\media\MediaSourceInterface $source */
$source = $manager->createInstance('test', []);
$source->submitConfigurationForm($form, $form_state);
$expected = ['source_field' => 'field_media_test_1', 'test_config_value' => 'Somewhere over the rainbow.'];
$this->assertSame($expected, $source->getConfiguration(), 'Submitted values were saved correctly.');
// Try to save a NULL value.
$form_state->setValue('test_config_value', NULL);
$source->submitConfigurationForm($form, $form_state);
$expected['test_config_value'] = NULL;
$this->assertSame($expected, $source->getConfiguration(), 'Submitted values were saved correctly.');
// Make sure that the config keys are determined correctly even if the
// existing value is NULL.
$form_state->setValue('test_config_value', 'Somewhere over the rainbow.');
$source->submitConfigurationForm($form, $form_state);
$expected['test_config_value'] = 'Somewhere over the rainbow.';
$this->assertSame($expected, $source->getConfiguration(), 'Submitted values were saved correctly.');
// Make sure that a non-relevant value will be skipped.
$form_state->setValue('not_relevant', 'Should not be saved in the plugin.');
$source->submitConfigurationForm($form, $form_state);
$this->assertSame($expected, $source->getConfiguration(), 'Submitted values were saved correctly.');
}
/**
* Tests different display options for the source field.
*/
public function testDifferentSourceFieldDisplays(): void {
$id = 'test_different_displays';
$field_name = 'field_media_different_display';
$this->createMediaTypeViaForm($id, $field_name);
// Source field not in displays.
$display = \Drupal::service('entity_display.repository')->getViewDisplay('media', $id);
$components = $display->getComponents();
$this->assertArrayHasKey($field_name, $components);
$this->assertSame('entity_reference_entity_id', $components[$field_name]['type']);
$display = \Drupal::service('entity_display.repository')->getFormDisplay('media', $id);
$components = $display->getComponents();
$this->assertArrayHasKey($field_name, $components);
$this->assertSame('entity_reference_autocomplete_tags', $components[$field_name]['type']);
}
/**
* Tests hidden source field in media type.
*/
public function testHiddenSourceField(): void {
$id = 'test_hidden_source_field';
$field_name = 'field_media_hidden';
$this->createMediaTypeViaForm($id, $field_name);
// Source field not in displays.
$display = \Drupal::service('entity_display.repository')->getViewDisplay('media', $id);
$this->assertArrayNotHasKey($field_name, $display->getComponents());
$display = \Drupal::service('entity_display.repository')->getFormDisplay('media', $id);
$this->assertArrayNotHasKey($field_name, $display->getComponents());
}
/**
* Creates a media type via form submit.
*
* @param string $source_plugin_id
* Source plugin ID.
* @param string $field_name
* Source field name.
*/
protected function createMediaTypeViaForm($source_plugin_id, $field_name): void {
/** @var \Drupal\media\MediaTypeInterface $type */
$type = MediaType::create(['source' => $source_plugin_id]);
$form = $this->container->get('entity_type.manager')
->getFormObject('media_type', 'add')
->setEntity($type);
$form_state = new FormState();
$form_state->setValues([
'label' => 'Test type',
'id' => $source_plugin_id,
'op' => 'Save and manage fields',
]);
/** @var \Drupal\Core\Entity\EntityFieldManagerInterface $field_manager */
$field_manager = \Drupal::service('entity_field.manager');
// Source field not created yet.
$fields = $field_manager->getFieldDefinitions('media', $source_plugin_id);
$this->assertArrayNotHasKey($field_name, $fields);
\Drupal::formBuilder()->submitForm($form, $form_state);
// Source field exists now.
$fields = $field_manager->getFieldDefinitions('media', $source_plugin_id);
$this->assertArrayHasKey($field_name, $fields);
}
}

View File

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Kernel;
use Drupal\media\Entity\Media;
/**
* Tests Media.
*
* @group media
*/
class MediaTest extends MediaKernelTestBase {
/**
* Tests various aspects of a media item.
*/
public function testEntity(): void {
$media = Media::create(['bundle' => $this->testMediaType->id()]);
$this->assertSame($media, $media->setOwnerId($this->user->id()), 'setOwnerId() method returns its own entity.');
}
/**
* Tests the Media "name" base field behavior.
*/
public function testNameBaseField(): void {
/** @var \Drupal\Core\Field\BaseFieldDefinition[] $field_definitions */
$field_definitions = $this->container->get('entity_field.manager')
->getBaseFieldDefinitions('media');
// Ensure media name is configurable on manage display.
$this->assertTrue($field_definitions['name']->isDisplayConfigurable('view'));
// Ensure it is not visible by default.
$this->assertSame($field_definitions['name']->getDisplayOptions('view'), ['region' => 'hidden']);
}
/**
* Tests permissions based on a media type have the correct permissions.
*/
public function testPermissions(): void {
$permissions = $this->container->get('user.permissions')->getPermissions();
$name = "create {$this->testMediaType->id()} media";
$this->assertArrayHasKey($name, $permissions);
$this->assertSame(['config' => [$this->testMediaType->getConfigDependencyName()]], $permissions[$name]['dependencies']);
}
}

View File

@ -0,0 +1,130 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Kernel;
use Drupal\entity_test\Entity\EntityTestBundle;
use Drupal\Tests\field\Traits\EntityReferenceFieldCreationTrait;
/**
* @coversDefaultClass \Drupal\media\Plugin\Field\FieldFormatter\MediaThumbnailFormatter
* @group media
*/
class MediaThumbnailFormatterTest extends MediaKernelTestBase {
use EntityReferenceFieldCreationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'entity_test',
];
/**
* Test media reference field name.
*
* @var string
*/
protected $mediaFieldName = 'field_media';
/**
* Test entity type id.
*
* @var string
*/
protected $testEntityTypeId = 'entity_test_with_bundle';
/**
* Test entity bundle id.
*
* @var string
*/
protected $testEntityBundleId = 'entity_test_bundle';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('entity_test_with_bundle');
// Create an entity bundle that has a media reference field.
$entity_test_bundle = EntityTestBundle::create([
'id' => $this->testEntityBundleId,
]);
$entity_test_bundle->save();
$this->createEntityReferenceField(
$this->testEntityTypeId,
$this->testEntityBundleId,
$this->mediaFieldName,
$this->mediaFieldName,
'media'
);
}
/**
* Tests the settings summary.
*
* @param array $settings
* The settings to use for the formatter.
* @param array $expected_summary
* The expected settings summary.
*
* @covers ::settingsSummary
*
* @dataProvider providerTestSettingsSummary
*/
public function testSettingsSummary(array $settings, array $expected_summary): void {
/** @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display */
$display = \Drupal::service('entity_display.repository')->getViewDisplay($this->testEntityTypeId, $this->testEntityBundleId);
$display->setComponent($this->mediaFieldName, [
'type' => 'media_thumbnail',
'settings' => $settings,
]);
$formatter = $display->getRenderer($this->mediaFieldName);
$actual_summary = array_map('strval', $formatter->settingsSummary());
$this->assertSame($expected_summary, $actual_summary);
}
/**
* Data provider for testSettingsSummary().
*
* @return array[]
* An array of test data.
*/
public static function providerTestSettingsSummary(): array {
return [
'link to content' => [
[
'image_link' => 'content',
],
[
'Original image',
'Linked to content',
'Image loading: lazy',
],
],
'link to media' => [
[
'image_link' => 'media',
],
[
'Original image',
'Image loading: lazy',
'Linked to media item',
],
],
'link to nothing' => [
[
'image_link' => '',
],
[
'Original image',
'Image loading: lazy',
],
],
];
}
}

View File

@ -0,0 +1,107 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Kernel;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\content_translation\ContentTranslationHandler;
/**
* Tests multilingual fields logic.
*
* @group media
*/
class MediaTranslationTest extends MediaKernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['language' , 'content_translation'];
/**
* The test media translation type.
*
* @var \Drupal\media\MediaTypeInterface
*/
protected $testTranslationMediaType;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installConfig(['language']);
// Create a test media type for translations.
$this->testTranslationMediaType = $this->createMediaType('test_translation');
for ($i = 0; $i < 3; ++$i) {
$language_id = 'l' . $i;
ConfigurableLanguage::create([
'id' => $language_id,
'label' => $this->randomString(),
])->save();
file_put_contents('public://' . $language_id . '.png', '');
}
}
/**
* Tests translatable fields storage/retrieval.
*/
public function testTranslatableFieldSaveLoad(): void {
/** @var \Drupal\Core\Entity\EntityTypeInterface $entity_type */
$entity_type = $this->container->get('entity_type.manager')->getDefinition('media');
$this->assertTrue($entity_type->isTranslatable(), 'Media is translatable.');
// Check if the translation handler uses the content_translation handler.
$translation_handler_class = $entity_type->getHandlerClass('translation');
$this->assertEquals(ContentTranslationHandler::class, $translation_handler_class, 'Translation handler is set to use the content_translation handler.');
// Prepare the field translations.
$source_field_definition = $this->testTranslationMediaType->getSource()->getSourceFieldDefinition($this->testTranslationMediaType);
$source_field_storage = $source_field_definition->getFieldStorageDefinition();
/** @var \Drupal\Core\Entity\Sql\SqlContentEntityStorage $media_storage */
$media_storage = $this->container->get('entity_type.manager')->getStorage('media');
/** @var \Drupal\media\Entity\Media $media */
$media = $media_storage->create([
'bundle' => $this->testTranslationMediaType->id(),
'name' => 'Unnamed',
]);
$field_translations = [];
$available_langcodes = array_keys($this->container->get('language_manager')->getLanguages());
$media->set('langcode', reset($available_langcodes));
foreach ($available_langcodes as $langcode) {
$values = [];
for ($i = 0; $i < $source_field_storage->getCardinality(); $i++) {
$values[$i]['value'] = $this->randomString();
}
$field_translations[$langcode] = $values;
$translation = $media->hasTranslation($langcode) ? $media->getTranslation($langcode) : $media->addTranslation($langcode);
$translation->{$source_field_definition->getName()}->setValue($field_translations[$langcode]);
}
// Save and reload the field translations.
$media->save();
$media_storage->resetCache();
$media = $media_storage->load($media->id());
// Check if the correct source field values were saved/loaded.
foreach ($field_translations as $langcode => $items) {
/** @var \Drupal\media\MediaInterface $media_translation */
$media_translation = $media->getTranslation($langcode);
$result = TRUE;
foreach ($items as $delta => $item) {
$result = $result && $item['value'] == $media_translation->{$source_field_definition->getName()}[$delta]->value;
}
$this->assertTrue($result, "$langcode translation field value not correct.");
$this->assertSame('public://' . $langcode . '.png', $media_translation->getSource()->getMetadata($media_translation, 'thumbnail_uri'), "$langcode translation thumbnail metadata attribute is not correct.");
$this->assertSame('public://' . $langcode . '.png', $media_translation->get('thumbnail')->entity->getFileUri(), "$langcode translation thumbnail value is not correct.");
$this->assertEquals('Test Thumbnail ' . $langcode, $media_translation->getSource()->getMetadata($media_translation, 'test_thumbnail_alt'), "$langcode translation thumbnail alt metadata attribute is not correct.");
$this->assertSame('Test Thumbnail ' . $langcode, $media_translation->get('thumbnail')->alt, "$langcode translation thumbnail alt value is not correct.");
}
}
}

View File

@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Kernel;
use Drupal\KernelTests\Core\Config\ConfigEntityValidationTestBase;
use Drupal\Tests\media\Traits\MediaTypeCreationTrait;
/**
* Tests validation of media_type entities.
*
* @group media
*/
class MediaTypeValidationTest extends ConfigEntityValidationTestBase {
use MediaTypeCreationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['field', 'media', 'media_test_source', 'user', 'image'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('user');
$this->installEntitySchema('media');
$this->entity = $this->createMediaType('test', ['id' => 'test_media']);
}
/**
* {@inheritdoc}
*/
public function testImmutableProperties(array $valid_values = []): void {
// If we don't clear the previous settings here, we will get unrelated
// validation errors (in addition to the one we're expecting), because the
// settings from the *old* source won't match the config schema for the
// settings of the *new* source.
$this->entity->set('source_configuration', []);
$valid_values['source'] = 'image';
parent::testImmutableProperties($valid_values);
}
/**
* Tests that the media source plugin's existence is validated.
*/
public function testMediaSourceIsValidated(): void {
// The `source` property is immutable, so we need to clone the entity in
// order to cleanly change its immutable properties.
$this->entity = $this->entity->createDuplicate()
// The `id` property is thrown out by createDuplicate().
->set('id', 'test')
// We need to clear the current source configuration, or we will get
// validation errors because the old configuration is not supported by the
// new source.
->set('source_configuration', [])
->set('source', 'invalid');
$this->assertValidationErrors([
'source' => "The 'invalid' plugin does not exist.",
]);
}
}

View File

@ -0,0 +1,115 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Kernel;
use Drupal\Core\Render\HtmlResponse;
use Drupal\media\Controller\OEmbedIframeController;
use Drupal\media\OEmbed\Provider;
use Drupal\media\OEmbed\Resource;
use Drupal\TestTools\Random;
use Prophecy\Argument;
use Symfony\Component\HttpFoundation\Request;
/**
* @coversDefaultClass \Drupal\media\Controller\OEmbedIframeController
*
* @group media
*/
class OEmbedIframeControllerTest extends MediaKernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['media_test_oembed'];
/**
* Data provider for testBadHashParameter().
*
* @return array
* An array of test cases.OffCanvasDialogTest.php
*/
public static function providerBadHashParameter() {
return [
'no hash' => [
'',
],
'invalid hash' => [
Random::string(),
],
];
}
/**
* Tests validation of the 'hash' query string parameter.
*
* @param string $hash
* The 'hash' query string parameter.
*
* @dataProvider providerBadHashParameter
*
* @covers ::render
*/
public function testBadHashParameter($hash): void {
/** @var callable $controller */
$controller = $this->container
->get('controller_resolver')
->getControllerFromDefinition('\Drupal\media\Controller\OEmbedIframeController::render');
$this->assertIsCallable($controller);
$this->expectException('\Symfony\Component\HttpKernel\Exception\BadRequestHttpException');
$this->expectExceptionMessage('This resource is not available');
$request = new Request([
'url' => 'https://example.com/path/to/resource',
'hash' => $hash,
]);
$controller($request);
}
/**
* Tests that resources can be used in media_oembed_iframe preprocess.
*
* @see media_test_oembed_preprocess_media_oembed_iframe()
*
* @covers ::render
*/
public function testResourcePassedToPreprocess(): void {
$hash = $this->container->get('media.oembed.iframe_url_helper')
->getHash('', 0, 0);
$url_resolver = $this->prophesize('\Drupal\media\OEmbed\UrlResolverInterface');
$resource_fetcher = $this->prophesize('\Drupal\media\OEmbed\ResourceFetcherInterface');
$provider = new Provider('YouTube', 'https://youtube.com', [
[
'url' => 'https://youtube.com/foo',
],
]);
$resource = Resource::rich('<iframe src="https://youtube.com/watch?feature=oembed"></iframe>', 320, 240, $provider);
$resource_fetcher->fetchResource(Argument::cetera())->willReturn($resource);
$this->container->set('media.oembed.url_resolver', $url_resolver->reveal());
$this->container->set('media.oembed.resource_fetcher', $resource_fetcher->reveal());
$request = new Request([
'url' => '',
'hash' => $hash,
]);
$response = $this->container->get('html_response.attachments_processor')
->processAttachments(OEmbedIframeController::create($this->container)
->render($request));
assert($response instanceof HtmlResponse);
$content = $response->getContent();
// This query parameter is added by
// media_test_oembed_preprocess_media_oembed_iframe() for YouTube videos.
$this->assertStringContainsString('&pasta=rigatoni', $content);
$this->assertStringContainsString('test.css', $content);
$this->assertContains('yo_there', $response->getCacheableMetadata()->getCacheTags());
$this->assertStringContainsString('text/html', $response->headers->get('Content-Type'));
}
}

View File

@ -0,0 +1,131 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\media\Entity\Media;
use Drupal\media\OEmbed\Provider;
use Drupal\media\OEmbed\ResourceFetcher;
use Drupal\media\OEmbed\UrlResolverInterface;
use Drupal\media\Plugin\Validation\Constraint\OEmbedResourceConstraint;
use Drupal\media\Plugin\Validation\Constraint\OEmbedResourceConstraintValidator;
use Drupal\Tests\media\Traits\MediaTypeCreationTrait;
use Prophecy\Argument;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
/**
* @coversDefaultClass \Drupal\media\Plugin\Validation\Constraint\OEmbedResourceConstraintValidator
*
* @group media
*/
class OEmbedResourceConstraintValidatorTest extends KernelTestBase {
use MediaTypeCreationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = ['field', 'file', 'image', 'media', 'user'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('file');
$this->installEntitySchema('user');
$this->installEntitySchema('media');
}
/**
* @covers ::validate
*/
public function testValidateEmptySource(): void {
$media = Media::create([
'bundle' => $this->createMediaType('oembed:video')->id(),
]);
$constraint = new OEmbedResourceConstraint();
// The media item has an empty source value, so the constraint validator
// should add a violation and return early before invoking the URL resolver.
$context = $this->prophesize(ExecutionContextInterface::class);
$context->addViolation($constraint->invalidResourceMessage)->shouldBeCalled();
$url_resolver = $this->prophesize(UrlResolverInterface::class);
$url_resolver->getProviderByUrl(Argument::any())->shouldNotBeCalled();
$validator = new OEmbedResourceConstraintValidator(
$url_resolver->reveal(),
$this->container->get('media.oembed.resource_fetcher'),
$this->container->get('logger.factory')
);
$validator->initialize($context->reveal());
$validator->validate($this->getValue($media), $constraint);
}
/**
* @covers ::validate
*/
public function testValidateUrlResolverInvoked(): void {
$media = Media::create([
'bundle' => $this->createMediaType('oembed:video')->id(),
'field_media_oembed_video' => 'source value',
]);
$constraint = new OEmbedResourceConstraint();
$context = $this->prophesize(ExecutionContextInterface::class);
$provider = $this->prophesize(Provider::class);
$provider->getName()->willReturn('YouTube');
$url_resolver = $this->prophesize(UrlResolverInterface::class);
$url_resolver->getProviderByUrl(Argument::any())->willReturn($provider->reveal());
$url_resolver->getResourceUrl(Argument::any())->shouldBeCalledOnce();
$validator = new OEmbedResourceConstraintValidator(
$url_resolver->reveal(),
$this->prophesize(ResourceFetcher::class)->reveal(),
$this->container->get('logger.factory')
);
$validator->initialize($context->reveal());
$validator->validate($this->getValue($media), $constraint);
}
/**
* Wraps a media entity in an anonymous class to mock a field value.
*
* @param \Drupal\media\Entity\Media $media
* The media object.
*
* @return object
* The mock field value to validate.
*/
protected function getValue(Media $media) {
return new class ($media) {
/**
* The test entity.
*
* @var \Drupal\media\Entity\Media
*/
private $entity;
public function __construct($entity) {
$this->entity = $entity;
}
/**
* Returns the test entity.
*/
public function getEntity(): Media {
return $this->entity;
}
};
}
}

View File

@ -0,0 +1,189 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Kernel;
use Drupal\Component\Utility\Crypt;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Render\RenderContext;
use Drupal\media\Entity\Media;
use Drupal\media\OEmbed\Resource;
use Drupal\media\OEmbed\ResourceFetcherInterface;
use Drupal\media\OEmbed\UrlResolverInterface;
use Drupal\media\Plugin\media\Source\OEmbed;
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;
use GuzzleHttp\Psr7\Utils;
use Prophecy\Argument;
/**
* @coversDefaultClass \Drupal\media\Plugin\media\Source\OEmbed
*
* @group media
*/
class OEmbedSourceTest extends MediaKernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['media'];
/**
* @covers ::getMetadata
*/
public function testGetMetadata(): void {
$configuration = [
'source_field' => 'field_test_oembed',
];
$plugin = OEmbed::create($this->container, $configuration, 'oembed', []);
// Test that NULL is returned for a media item with no source value.
$media = $this->prophesize('\Drupal\media\MediaInterface');
$field_items = $this->prophesize('\Drupal\Core\Field\FieldItemListInterface');
$field_items->isEmpty()->willReturn(TRUE);
$media->get($configuration['source_field'])->willReturn($field_items->reveal());
$this->assertNull($plugin->getMetadata($media->reveal(), 'type'));
}
/**
* Data provider for ::testThumbnailUri().
*
* @return array
* Sets of arguments to pass to the test method.
*/
public static function providerThumbnailUri(): array {
return [
'no query string, extension in URL' => [
'internal:/core/misc/druplicon.png',
[],
'png',
],
'with query string, extension in URL' => [
'internal:/core/misc/druplicon.png?foo=bar',
[],
'png',
],
'no query string or extension in URL, has MIME type' => [
'internal:/core/misc/druplicon',
[
'Content-Type' => ['image/png'],
],
'png',
],
'query string but no extension in URL, has MIME type' => [
'internal:/core/misc/druplicon?pasta=ravioli',
[
'Content-Type' => ['image/png'],
],
'png',
],
'no query string, MIME type, or extension in URL' => [
'internal:/core/misc/druplicon',
[],
'',
],
];
}
/**
* Tests that remote thumbnails are downloaded correctly.
*
* @param string $remote_thumbnail_url
* The URL of the remote thumbnail. This will be wired up to a mocked
* response containing the data from core/misc/druplicon.png.
* @param array[] $thumbnail_headers
* If the thumbnail's file extension cannot be determined from its URL, an
* attempt will be made to derive the extension from the response's
* Content-Type header. This array contains the headers that should be
* returned with the thumbnail response, where the keys are header names and
* the values are arrays of strings.
* @param string $expected_extension
* The extension that the downloaded thumbnail should have.
*
* @covers ::getLocalThumbnailUri
*
* @dataProvider providerThumbnailUri
*/
public function testThumbnailUri(string $remote_thumbnail_url, array $thumbnail_headers, string $expected_extension): void {
// Create a fake resource with the given thumbnail URL.
$resource = Resource::rich('<html></html>', 16, 16, NULL, 'Test resource', NULL, NULL, NULL, $remote_thumbnail_url, 16, 16);
$thumbnail_url = $resource->getThumbnailUrl()->toString();
// There's no need to resolve the resource URL in this test; we just need
// to fetch the resource.
$this->container->set(
'media.oembed.url_resolver',
$this->prophesize(UrlResolverInterface::class)->reveal()
);
// Mock the resource fetcher so that it will return our fake resource.
$resource_fetcher = $this->prophesize(ResourceFetcherInterface::class);
$resource_fetcher->fetchResource(Argument::any())
->willReturn($resource);
$this->container->set('media.oembed.resource_fetcher', $resource_fetcher->reveal());
// The source plugin will try to fetch the remote thumbnail, so mock the
// HTTP client to ensure that request returns a response with some valid
// image data.
$data = Utils::tryFopen($this->getDrupalRoot() . '/core/misc/druplicon.png', 'r');
$response = new Response(200, $thumbnail_headers, Utils::streamFor($data));
$handler = new MockHandler([$response]);
$client = new Client([
'handler' => new HandlerStack($handler),
]);
$this->container->set('http_client', $client);
$media_type = $this->createMediaType('oembed:video');
$source = $media_type->getSource();
// Add some HTML to the global site slogan, and use the site:slogan token in
// the thumbnail path, in order to prove that the final thumbnail path is
// stripped of HTML tags, and XML entities are decoded.
$this->config('system.site')
->set('slogan', '<h1>this&amp;that</h1>')
->save();
$configuration = $source->getConfiguration();
$configuration['thumbnails_directory'] .= '/[site:slogan]';
$source->setConfiguration($configuration);
$media_type->save();
$media = Media::create([
'bundle' => $media_type->id(),
$source->getSourceFieldDefinition($media_type)->getName() => $this->randomString(),
]);
$media->save();
// The thumbnail directory should include the current date, as per the
// default configuration of the oEmbed source plugin.
$date = date('Y-m', $this->container->get('datetime.time')->getRequestTime());
// The thumbnail should have a file extension, even if it wasn't in the URL.
$expected_uri = "public://oembed_thumbnails/$date/this&that/" . Crypt::hashBase64($thumbnail_url) . ".$expected_extension";
$this->assertSame($expected_uri, $source->getMetadata($media, 'thumbnail_uri'));
// Even if we get the thumbnail_uri more than once, it should only be
// downloaded once. The HTTP client will throw an exception if we try to
// do another request without setting up another response.
$source->getMetadata($media, 'thumbnail_uri');
// The downloaded thumbnail should be usable by the image toolkit.
$this->assertFileExists($expected_uri);
/** @var \Drupal\Core\Image\Image $image */
$image = $this->container->get('image.factory')->get($expected_uri);
$this->assertTrue($image->isValid());
// Check that the current date token as per the default configuration of the
// oEmbed source plugin doesn't make a render context uncacheable.
$context = new RenderContext();
\Drupal::service('renderer')->executeInRenderContext($context, function () use ($source, $media) {
return $source->getMetadata($media, 'thumbnail_uri');
});
/** @var \Drupal\Core\Render\BubbleableMetadata $bubbleable_metadata */
$bubbleable_metadata = $context->pop();
$this->assertSame(Cache::PERMANENT, $bubbleable_metadata->getCacheMaxAge());
}
}

View File

@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Kernel;
use Drupal\media\OEmbed\ProviderException;
use GuzzleHttp\Psr7\Utils;
/**
* Tests the oEmbed provider repository.
*
* @covers \Drupal\media\OEmbed\ProviderRepository
*
* @group media
*/
class ProviderRepositoryTest extends MediaKernelTestBase {
/**
* Tests that provider discovery fails if the provider database is empty.
*
* @param string $content
* The expected JSON content of the provider database.
*
* @dataProvider providerEmptyProviderList
*/
public function testEmptyProviderList($content): void {
$response = $this->prophesize('\GuzzleHttp\Psr7\Response');
$response->getBody()->willReturn(Utils::streamFor($content));
$client = $this->createMock('\GuzzleHttp\Client');
$client->method('request')->withAnyParameters()->willReturn($response->reveal());
$this->container->set('http_client', $client);
$this->expectException(ProviderException::class);
$this->expectExceptionMessage('Remote oEmbed providers database returned invalid or empty list.');
$this->container->get('media.oembed.provider_repository')->getAll();
}
/**
* Data provider for testEmptyProviderList().
*
* @see ::testEmptyProviderList()
*
* @return array
* An array of test cases.
*/
public static function providerEmptyProviderList() {
return [
'empty array' => ['[]'],
'empty string' => [''],
];
}
/**
* Tests that provider discovery fails with a non-existent provider database.
*
* @param string $providers_url
* The URL of the provider database.
* @param string $exception_message
* The expected exception message.
*
* @dataProvider providerNonExistingProviderDatabase
*/
public function testNonExistingProviderDatabase($providers_url, $exception_message): void {
$this->config('media.settings')
->set('oembed_providers_url', $providers_url)
->save();
$this->expectException(ProviderException::class);
$this->expectExceptionMessage($exception_message);
$this->container->get('media.oembed.provider_repository')->getAll();
}
/**
* Data provider for testEmptyProviderList().
*
* @see ::testEmptyProviderList()
*
* @return array
* An array of test cases.
*/
public static function providerNonExistingProviderDatabase() {
return [
[
'http://oembed1.com/providers.json',
'Could not retrieve the oEmbed provider database from http://oembed1.com/providers.json',
],
[
'http://oembed.com/providers1.json',
'Could not retrieve the oEmbed provider database from http://oembed.com/providers1.json',
],
];
}
}

View File

@ -0,0 +1,172 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Kernel\Views;
use Drupal\media\Entity\Media;
use Drupal\Tests\user\Traits\UserCreationTrait;
use Drupal\Tests\views\Kernel\ViewsKernelTestBase;
use Drupal\views\Tests\ViewResultAssertionTrait;
use Drupal\views\Tests\ViewTestData;
use Drupal\views\Views;
use Drupal\Tests\media\Traits\MediaTypeCreationTrait;
/**
* Tests the media_revision_user field.
*
* @group media
*/
class RevisionUserTest extends ViewsKernelTestBase {
use UserCreationTrait;
use ViewResultAssertionTrait;
use MediaTypeCreationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'media',
'media_test_views',
'media_test_source',
'system',
'user',
'views',
'image',
'field',
'file',
];
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_media_revision_uid'];
/**
* The test media type.
*
* @var \Drupal\media\MediaTypeInterface
*/
protected $testMediaType;
/**
* Map column names.
*
* @var array
*/
public static $columnMap = [
'mid' => 'mid',
'vid' => 'vid',
'uid' => 'uid',
'revision_user' => 'revision_user',
];
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE): void {
parent::setUp($import_test_views);
$this->installEntitySchema('media');
$this->installEntitySchema('file');
$this->installSchema('file', 'file_usage');
$this->installEntitySchema('user');
$this->installConfig(['field', 'system', 'image', 'file', 'media']);
if ($import_test_views) {
ViewTestData::createTestViews(get_class($this), ['media_test_views']);
}
$this->testMediaType = $this->createMediaType('test');
}
/**
* Tests the media_revision_user relationship.
*/
public function testRevisionUser(): void {
$primary_author = $this->createUser();
$secondary_author = $this->createUser();
$media = Media::create([
'name' => 'Test media',
'bundle' => $this->testMediaType->id(),
'uid' => $primary_author->id(),
]);
$media->setRevisionUserId($primary_author->id());
$media->save();
$view = Views::getView('test_media_revision_uid');
$this->executeView($view);
$this->assertIdenticalResultset($view, [
[
'mid' => 1,
'vid' => 1,
'uid' => $primary_author->id(),
'revision_user' => $primary_author->id(),
],
], static::$columnMap);
// Test results shows the original author as well as the revision author.
$media->setRevisionUser($secondary_author);
$media->setNewRevision();
$media->save();
$view = Views::getView('test_media_revision_uid');
$this->executeView($view);
$this->assertIdenticalResultset($view, [
[
'mid' => 1,
'vid' => 2,
'uid' => $primary_author->id(),
'revision_user' => $secondary_author->id(),
],
], static::$columnMap);
// Build a larger dataset to allow filtering.
$media2_name = $this->randomString();
$media2 = Media::create([
'name' => $media2_name,
'bundle' => $this->testMediaType->id(),
'uid' => $primary_author->id(),
]);
$media2->save();
$media2->setRevisionUser($primary_author);
$media2->setNewRevision();
$media2->save();
$view = Views::getView('test_media_revision_uid');
$this->executeView($view);
$this->assertIdenticalResultset($view, [
[
'mid' => 1,
'vid' => 2,
'uid' => $primary_author->id(),
'revision_user' => $secondary_author->id(),
],
[
'mid' => 2,
'vid' => 4,
'uid' => $primary_author->id(),
'revision_user' => $primary_author->id(),
],
], static::$columnMap);
// Test filter by revision_user.
$view = Views::getView('test_media_revision_uid');
$view->initHandlers();
$view->filter['revision_user']->value = [$secondary_author->id()];
$this->executeView($view);
$this->assertIdenticalResultset($view, [
[
'mid' => 1,
'vid' => 2,
'uid' => $primary_author->id(),
'revision_user' => $secondary_author->id(),
],
], static::$columnMap);
}
}

View File

@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Traits;
use Drupal\media\Entity\MediaType;
/**
* Provides methods to create a media type from given values.
*
* This trait is meant to be used only by test classes.
*/
trait MediaTypeCreationTrait {
/**
* Create a media type for a source plugin.
*
* @param string $source_plugin_id
* The media source plugin ID.
* @param mixed[] $values
* (optional) Additional values for the media type entity:
* - id: The ID of the media type. If none is provided, a random value will
* be used.
* - label: The human-readable label of the media type. If none is provided,
* a random value will be used.
* See \Drupal\media\MediaTypeInterface and \Drupal\media\Entity\MediaType
* for full documentation of the media type properties.
*
* @return \Drupal\media\MediaTypeInterface
* A media type.
*
* @see \Drupal\media\MediaTypeInterface
* @see \Drupal\media\Entity\MediaType
*/
protected function createMediaType($source_plugin_id, array $values = []) {
$values += [
'id' => $this->randomMachineName(),
'label' => $this->randomString(),
'source' => $source_plugin_id,
];
/** @var \Drupal\media\MediaTypeInterface $media_type */
$media_type = MediaType::create($values);
$source = $media_type->getSource();
$source_field = $source->createSourceField($media_type);
$source_configuration = $source->getConfiguration();
$source_configuration['source_field'] = $source_field->getName();
$source->setConfiguration($source_configuration);
$this->assertSame(SAVED_NEW, $media_type->save());
// The media type form creates a source field if it does not exist yet. The
// same must be done in a kernel test, since it does not use that form.
// @see \Drupal\media\MediaTypeForm::save()
/** @var \Drupal\field\FieldStorageConfigInterface $storage */
$storage = $source_field->getFieldStorageDefinition();
$storage->save();
// The source field storage has been created, now the field can be saved.
$source_field->save();
// Add the source field to the form display for the media type.
$form_display = \Drupal::service('entity_display.repository')->getFormDisplay('media', $media_type->id(), 'default');
$source->prepareFormDisplay($media_type, $form_display);
$form_display->save();
return $media_type;
}
}

View File

@ -0,0 +1,99 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Traits;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Url;
use Drupal\media\OEmbed\Provider;
/**
* Contains helper functions for testing oEmbed functionality in isolation.
*/
trait OEmbedTestTrait {
/**
* Returns the relative path to the oEmbed fixtures directory.
*
* @return string
* The relative path to the oEmbed fixtures directory.
*/
protected function getFixturesDirectory(): string {
return \Drupal::service('extension.list.module')->getPath('media') . '/tests/fixtures/oembed';
}
/**
* Returns the absolute URL of the oEmbed fixtures directory.
*
* @return string
* The absolute URL of the oEmbed fixtures directory.
*/
protected function getFixturesUrl(): string {
return $this->baseUrl . '/' . $this->getFixturesDirectory();
}
/**
* Forces Media to use the provider database in the fixtures directory.
*/
protected function useFixtureProviders() {
$this->config('media.settings')
->set('oembed_providers_url', $this->getFixturesUrl() . '/providers.json')
->save();
}
/**
* Configures the HTTP client to always use the fixtures directory.
*
* All requests are carried out relative to the URL of the fixtures directory.
* For example, after calling this method, a request for foobar.html will
* actually request http://test-site/path/to/fixtures/foobar.html.
*/
protected function lockHttpClientToFixtures() {
$this->writeSettings([
'settings' => [
'http_client_config' => [
'base_uri' => (object) [
'value' => $this->getFixturesUrl() . '/',
'required' => TRUE,
],
],
],
]);
// Rebuild the container in case there is already an instantiated service
// that has a dependency on the http_client service.
$this->container->get('kernel')->rebuildContainer();
$this->container = $this->container->get('kernel')->getContainer();
}
/**
* Ensures that oEmbed provider endpoints use the test resource route.
*
* All oEmbed provider endpoints defined in the fixture providers.json will
* use the media_test_oembed.resource.get route as their URL.
*
* This requires the media_test_oembed module in order to work.
*/
protected function hijackProviderEndpoints() {
$providers = $this->getFixturesDirectory() . '/providers.json';
$providers = file_get_contents($providers);
$providers = Json::decode($providers);
$endpoint_url = Url::fromRoute('media_test_oembed.resource.get')
->setAbsolute()
->toString();
/** @var \Drupal\media_test_oembed\ProviderRepository $provider_repository */
$provider_repository = $this->container->get('media.oembed.provider_repository');
foreach ($providers as &$provider) {
foreach ($provider['endpoints'] as &$endpoint) {
$endpoint['url'] = $endpoint_url;
}
$provider_repository->setProvider(
new Provider($provider['provider_name'], $provider['provider_url'], $provider['endpoints'])
);
}
}
}

View File

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Unit;
use Drupal\media\OEmbed\Endpoint;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\media\OEmbed\Endpoint
*
* @group media
*/
class EndpointTest extends UnitTestCase {
/**
* @covers ::matchUrl
*/
public function testMatchUrl(): void {
$endpoint = new Endpoint(
'https://www.youtube.com/oembed',
$this->createMock('\Drupal\media\OEmbed\Provider'),
['https://*.youtube.com/playlist?list=*']
);
$this->assertTrue($endpoint->matchUrl('https://www.youtube.com/playlist?list=aBc-EzAs123'));
}
/**
* @covers ::matchUrl
*/
public function testCaseSensitiveMatch(): void {
$endpoint = new Endpoint(
'https://www.example.com/oembed',
$this->createMock('\Drupal\media\OEmbed\Provider'),
['https://*.example.com/Video/*'],
);
$this->assertTrue($endpoint->matchUrl('https://foo.example.com/Video/bar'));
}
}

View File

@ -0,0 +1,95 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Unit;
use Drupal\Core\PrivateKey;
use Drupal\Core\Routing\RequestContext;
use Drupal\media\IFrameUrlHelper;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\media\IFrameUrlHelper
*
* @group media
*/
class IFrameUrlHelperTest extends UnitTestCase {
/**
* Data provider for testIsSecure().
*
* @see ::testIsSecure()
*
* @return array
* An array of test data.
*/
public static function providerIsSecure() {
return [
'no domain' => [
'/path/to/media.php',
'http://www.example.com/',
FALSE,
],
'no base URL domain' => [
'http://www.example.com/media.php',
'/invalid/base/url',
FALSE,
],
'same domain' => [
'http://www.example.com/media.php',
'http://www.example.com/',
FALSE,
],
'different domain' => [
'http://www.example.com/media.php',
'http://www.example-assets.com/',
TRUE,
],
'same subdomain' => [
'http://foo.example.com/media.php',
'http://foo.example.com/',
FALSE,
],
'different subdomain' => [
'http://assets.example.com/media.php',
'http://foo.example.com/',
TRUE,
],
'subdomain and top-level domain' => [
'http://assets.example.com/media.php',
'http://example.com/',
TRUE,
],
];
}
/**
* Tests that isSecure() behaves properly.
*
* @param string $url
* The URL to test for security.
* @param string $base_url
* The base URL to compare $url against.
* @param bool $secure
* The expected result of isSecure().
*
* @covers ::isSecure
*
* @dataProvider providerIsSecure
*/
public function testIsSecure($url, $base_url, $secure): void {
$request_context = $this->createMock(RequestContext::class);
$request_context->expects($this->any())
->method('getCompleteBaseUrl')
->willReturn($base_url);
$url_helper = new IFrameUrlHelper(
$request_context,
$this->createMock(PrivateKey::class)
);
$this->assertSame($secure, $url_helper->isSecure($url));
}
}

View File

@ -0,0 +1,252 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Unit;
use Drupal\Core\KeyValueStore\KeyValueMemoryFactory;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\media\OEmbed\ProviderException;
use Drupal\media\OEmbed\ProviderRepository;
use Drupal\Tests\UnitTestCase;
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;
/**
* @coversDefaultClass \Drupal\media\OEmbed\ProviderRepository
*
* @group media
*/
class ProviderRepositoryTest extends UnitTestCase {
/**
* The provider repository under test.
*
* @var \Drupal\media\OEmbed\ProviderRepository
*/
private $repository;
/**
* The HTTP client handler which will serve responses.
*
* @var \GuzzleHttp\Handler\MockHandler
*/
private $responses;
/**
* The key-value store.
*
* @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
*/
private $keyValue;
/**
* The time that the current test began.
*
* @var int
*/
private $currentTime;
/**
* The mocked logger channel.
*
* @var \Psr\Log\LoggerInterface|\Prophecy\Prophecy\ObjectProphecy
*/
private $logger;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$config_factory = $this->getConfigFactoryStub([
'media.settings' => [
'oembed_providers_url' => 'https://oembed.com/providers.json',
],
]);
$key_value_factory = new KeyValueMemoryFactory();
$this->keyValue = $key_value_factory->get('media');
$this->currentTime = time();
$time = $this->prophesize('\Drupal\Component\Datetime\TimeInterface');
$time->getCurrentTime()->willReturn($this->currentTime);
$this->logger = $this->prophesize('\Psr\Log\LoggerInterface');
$logger_factory = $this->prophesize(LoggerChannelFactoryInterface::class);
$logger_factory->get('media')->willReturn($this->logger);
$this->responses = new MockHandler();
$client = new Client([
'handler' => HandlerStack::create($this->responses),
]);
$this->repository = new ProviderRepository(
$client,
$config_factory,
$time->reveal(),
$key_value_factory,
$logger_factory->reveal()
);
}
/**
* Tests that a successful fetch stores the provider database in key-value.
*/
public function testSuccessfulFetch(): void {
$body = <<<END
[
{
"provider_name": "YouTube",
"provider_url": "https:\/\/www.youtube.com\/",
"endpoints": [
{
"schemes": [
"https:\/\/*.youtube.com\/watch*",
"https:\/\/*.youtube.com\/v\/*"
],
"url": "https:\/\/www.youtube.com\/oembed",
"discovery": true
}
]
}
]
END;
$response = new Response(200, [], $body);
$this->responses->append($response);
$provider = $this->repository->get('YouTube');
$stored_data = [
'data' => [
'YouTube' => $provider,
],
'expires' => $this->currentTime + 604800,
];
$this->assertSame($stored_data, $this->keyValue->get('oembed_providers'));
}
/**
* Tests handling of invalid JSON when fetching the provider database.
*
* @param int $expiration_offset
* An offset to add to the current time to determine when the primed data,
* if any, expires.
*
* @dataProvider providerInvalidResponse
*/
public function testInvalidResponse(int $expiration_offset): void {
$provider = $this->prophesize('\Drupal\media\OEmbed\Provider')
->reveal();
// This stored data should be returned, irrespective of whether it's fresh.
$this->keyValue->set('oembed_providers', [
'data' => [
'YouTube' => $provider,
],
'expires' => $this->currentTime + $expiration_offset,
]);
$response = new Response(200, [], "This certainly isn't valid JSON.");
$this->responses->append($response, $response);
$this->assertSame($provider, $this->repository->get('YouTube'));
// When there is no stored data, we should get an exception.
$this->keyValue->delete('oembed_providers');
$this->expectException(ProviderException::class);
$this->repository->get('YouTube');
}
/**
* Data provider for ::testInvalidResponse().
*
* @return array[]
* Sets of arguments to pass to the test method.
*/
public static function providerInvalidResponse(): array {
return [
'expired' => [
-86400,
],
'fresh' => [
86400,
],
];
}
/**
* Tests handling of exceptions when fetching the provider database.
*/
public function testRequestException(): void {
$provider = $this->prophesize('\Drupal\media\OEmbed\Provider')
->reveal();
// This data is expired (stale), but it should be returned anyway.
$this->keyValue->set('oembed_providers', [
'data' => [
'YouTube' => $provider,
],
'expires' => $this->currentTime - 86400,
]);
$response = new Response(503);
$this->responses->append($response, $response);
$this->assertSame($provider, $this->repository->get('YouTube'));
// When there is no stored data, we should get an exception.
$this->keyValue->delete('oembed_providers');
$this->expectException(ProviderException::class);
$this->repository->get('YouTube');
}
/**
* Tests a successful fetch but with a single corrupt item.
*/
public function testCorruptProviderIgnored(): void {
$body = <<<END
[
{
"provider_name": "YouTube",
"provider_url": "https:\/\/www.youtube.com\/",
"endpoints": [
{
"schemes": [
"https:\/\/*.youtube.com\/watch*",
"https:\/\/*.youtube.com\/v\/*"
],
"url": "https:\/\/www.youtube.com\/oembed",
"discovery": true
}
]
},
{
"provider_name": "Uncle Rico's football videos",
"provider_url": "not a real url",
"endpoints": []
}
]
END;
$response = new Response(200, [], $body);
$this->responses->append($response);
// The corrupt provider should cause a warning to be logged.
$this->logger->warning(
"Provider Uncle Rico's football videos does not define a valid external URL.",
)->shouldBeCalled();
$youtube = $this->repository->get('YouTube');
// The corrupt provider should not be stored.
$stored_data = [
'data' => [
'YouTube' => $youtube,
],
'expires' => $this->currentTime + 604800,
];
$this->assertSame($stored_data, $this->keyValue->get('oembed_providers'));
$this->expectException('InvalidArgumentException');
$this->repository->get("Uncle Rico's football videos");
}
}

View File

@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Unit;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Cache\NullBackend;
use Drupal\media\OEmbed\ResourceException;
use Drupal\media\OEmbed\ResourceFetcher;
use Drupal\Tests\UnitTestCase;
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;
use GuzzleHttp\RequestOptions;
/**
* @coversDefaultClass \Drupal\media\OEmbed\ResourceFetcher
*
* @group media
*/
class ResourceFetcherTest extends UnitTestCase {
/**
* Tests that resources are fetched with a hard-coded timeout.
*/
public function testFetchTimeout(): void {
$url = 'https://example.com/oembed?url=resource';
$headers = [
'Content-Type' => ['text/javascript'],
];
$body = Json::encode([
'version' => '1.0',
'type' => 'video',
'html' => 'test',
]);
$response = new Response(200, $headers, $body);
$non_default_timeout = 10;
$client = $this->prophesize(Client::class);
$client->request('GET', $url, [RequestOptions::TIMEOUT => $non_default_timeout])
->shouldBeCalled()
->willReturn($response);
$fetcher = new ResourceFetcher(
$client->reveal(),
$this->createMock('\Drupal\media\OEmbed\ProviderRepositoryInterface'),
new NullBackend('default'),
$non_default_timeout
);
$fetcher->fetchResource($url);
}
/**
* Tests how the resource fetcher handles unknown Content-Type headers.
*
* @covers ::fetchResource
*/
public function testUnknownContentTypeHeader(): void {
$headers = [
'Content-Type' => ['text/html'],
];
$body = Json::encode([
'version' => '1.0',
'type' => 'video',
'html' => 'test',
]);
$valid_response = new Response(200, $headers, $body);
// Strip off the trailing '}' to produce a response that will cause a JSON
// parse error.
$invalid_response = new Response(200, $headers, rtrim($body, '}'));
// A response that is valid JSON, but does not decode to an array, should
// produce an exception as well.
$non_array_response = new Response(200, $headers, '"Valid JSON, but not an array..."');
$mock_handler = new MockHandler([
$valid_response,
$invalid_response,
$non_array_response,
]);
$client = new Client([
'handler' => HandlerStack::create($mock_handler),
]);
$providers = $this->createMock('\Drupal\media\OEmbed\ProviderRepositoryInterface');
$fetcher = new ResourceFetcher($client, $providers, new NullBackend('default'));
/** @var \Drupal\media\OEmbed\Resource $resource */
$resource = $fetcher->fetchResource('valid');
// The resource should have been successfully decoded as JSON.
$this->assertSame('video', $resource->getType());
$this->assertSame('test', $resource->getHtml());
// Invalid JSON should throw an exception.
try {
$fetcher->fetchResource('invalid');
$this->fail('Expected a ResourceException to be thrown for invalid JSON.');
}
catch (ResourceException $e) {
$this->assertSame('Error decoding oEmbed resource: Syntax error', $e->getMessage());
}
// Valid JSON that does not produce an array should also throw an exception.
$this->expectException(ResourceException::class);
$this->expectExceptionMessage('The oEmbed resource could not be decoded.');
$fetcher->fetchResource('non_array');
}
}

View File

@ -0,0 +1,90 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\media\Unit;
use Drupal\media\OEmbed\Resource;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\media\OEmbed\Resource
* @group media
*/
class ResourceTest extends UnitTestCase {
/**
* Test cases for ::testSetDimensions.
*/
public static function setDimensionsTestCases() {
return [
'Standard rich dimensions' => [
'rich',
5,
10,
],
'Negative width and height' => [
'rich',
-5,
-10,
'The dimensions must be NULL or numbers greater than zero.',
],
'Zero width' => [
'rich',
0,
5,
'The dimensions must be NULL or numbers greater than zero.',
],
'NULL width' => [
'rich',
NULL,
10,
],
'NULL height' => [
'rich',
NULL,
10,
],
'NULL width and height' => [
'rich',
NULL,
NULL,
],
'Cast numeric dimensions' => [
'rich',
"1",
"45",
NULL,
1,
45,
],
'Cast invalid zero value' => [
'rich',
"0",
10,
'The dimensions must be NULL or numbers greater than zero.',
],
'Cast negative value' => [
'rich',
"-10",
10,
'The dimensions must be NULL or numbers greater than zero.',
],
];
}
/**
* @covers ::setDimensions
* @dataProvider setDimensionsTestCases
*/
public function testSetDimensions($factory, $width, $height, $exception = NULL, $expected_width = NULL, $expected_height = NULL): void {
if ($exception) {
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage($exception);
}
$resource = Resource::$factory('foo', $width, $height);
$this->assertSame($expected_width ?: $width, $resource->getWidth());
$this->assertSame($expected_height ?: $height, $resource->getHeight());
}
}