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,18 @@
# Schema for the configuration files of the Responsive image test theme module.
field.formatter.settings.responsive_image_test:
type: mapping
label: 'Responsive image list format settings'
mapping:
responsive_image_style:
type: string
label: 'Responsive image style'
image_link:
type: string
label: 'Link image to'
image_loading:
type: mapping
label: 'Image loading settings'
mapping:
attribute:
type: string
label: 'Loading attribute'

View File

@ -0,0 +1,33 @@
responsive_image_test_module.empty:
label: empty
mediaQuery: ''
weight: 0
multipliers:
- 1x
- 1.5x
- 2x
responsive_image_test_module.mobile:
label: mobile
mediaQuery: '(min-width: 0px)'
weight: 1
multipliers:
- 1x
- 1.5x
- 2x
responsive_image_test_module.narrow:
label: narrow
mediaQuery: '(min-width: 560px)'
weight: 2
multipliers:
- 1x
- 1.5x
- 2x
responsive_image_test_module.wide:
label: wide
mediaQuery: '(min-width: 851px)'
weight: 3
multipliers:
- 1x
- 1.5x
- 2x

View File

@ -0,0 +1,5 @@
name: 'Responsive image test theme'
type: module
description: 'Test theme for responsive image.'
package: Testing
version: VERSION

View File

@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Drupal\responsive_image_test_module\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\Attribute\FieldFormatter;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\responsive_image\Plugin\Field\FieldFormatter\ResponsiveImageFormatter;
use Drupal\Core\Field\FieldItemListInterface;
/**
* Plugin to test responsive image formatter.
*/
#[FieldFormatter(
id: 'responsive_image_test',
label: new TranslatableMarkup('Responsive image test'),
field_types: [
'image',
],
)]
class ResponsiveImageTestFormatter extends ResponsiveImageFormatter {
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = parent::viewElements($items, $langcode);
// Unset #item_attributes to test that the theme function can handle that.
foreach ($elements as &$element) {
if (isset($element['#item_attributes'])) {
unset($element['#item_attributes']);
}
}
return $elements;
}
}

View File

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

View File

@ -0,0 +1,159 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\responsive_image\Functional;
use Drupal\responsive_image\ResponsiveImageStyleInterface;
use Drupal\Tests\BrowserTestBase;
// cspell:ignore modulenarrow
/**
* Thoroughly test the administrative interface of the Responsive Image module.
*
* @group responsive_image
*/
class ResponsiveImageAdminUITest extends BrowserTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'responsive_image',
'responsive_image_test_module',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->drupalLogin($this->drupalCreateUser([
'administer responsive images',
]));
}
/**
* Tests responsive image administration functionality.
*/
public function testResponsiveImageAdmin(): void {
// We start without any default styles.
$this->drupalGet('admin/config/media/responsive-image-style');
$this->assertSession()->pageTextContains('There are no responsive image styles yet.');
// Add a responsive image style.
$this->drupalGet('admin/config/media/responsive-image-style/add');
// The 'Responsive Image' breakpoint group should be selected by default.
$this->assertSession()->fieldValueEquals('breakpoint_group', 'responsive_image');
// Create a new group.
$edit = [
'label' => 'Style One',
'id' => 'style_one',
'breakpoint_group' => 'responsive_image',
'fallback_image_style' => 'thumbnail',
];
$this->drupalGet('admin/config/media/responsive-image-style/add');
$this->submitForm($edit, 'Save');
// Check if the new group is created.
$this->assertSession()->statusCodeEquals(200);
$this->drupalGet('admin/config/media/responsive-image-style');
$this->assertSession()->pageTextNotContains('There are no responsive image styles yet.');
$this->assertSession()->pageTextContains('Style One');
// Edit the breakpoint_group.
$this->drupalGet('admin/config/media/responsive-image-style/style_one');
$this->assertSession()->fieldValueEquals('label', 'Style One');
$this->assertSession()->fieldValueEquals('breakpoint_group', 'responsive_image');
$edit = [
'breakpoint_group' => 'responsive_image_test_module',
];
$this->submitForm($edit, 'Save');
// Edit the group.
$this->drupalGet('admin/config/media/responsive-image-style/style_one');
$this->assertSession()->fieldValueEquals('label', 'Style One');
$this->assertSession()->fieldValueEquals('breakpoint_group', 'responsive_image_test_module');
$this->assertSession()->fieldValueEquals('fallback_image_style', 'thumbnail');
$cases = [
['mobile', '1x'],
['mobile', '2x'],
['narrow', '1x'],
['narrow', '2x'],
['wide', '1x'],
['wide', '2x'],
];
$image_styles = array_merge(
[ResponsiveImageStyleInterface::EMPTY_IMAGE, ResponsiveImageStyleInterface::ORIGINAL_IMAGE],
array_keys(image_style_options(FALSE))
);
foreach ($cases as $case) {
// Check if the radio buttons are present.
$this->assertSession()->fieldExists('keyed_styles[responsive_image_test_module.' . $case[0] . '][' . $case[1] . '][image_mapping_type]');
// Check if the image style dropdowns are present.
$this->assertSession()->fieldExists('keyed_styles[responsive_image_test_module.' . $case[0] . '][' . $case[1] . '][image_style]');
// Check if the sizes textfields are present.
$this->assertSession()->fieldExists('keyed_styles[responsive_image_test_module.' . $case[0] . '][' . $case[1] . '][sizes]');
foreach ($image_styles as $image_style_name) {
// Check if the image styles are available in the dropdowns.
$this->assertSession()->optionExists('keyed_styles[responsive_image_test_module.' . $case[0] . '][' . $case[1] . '][image_style]', $image_style_name);
// Check if the image styles checkboxes are present.
$this->assertSession()->fieldExists('keyed_styles[responsive_image_test_module.' . $case[0] . '][' . $case[1] . '][sizes_image_styles][' . $image_style_name . ']');
}
}
// Save styles for 1x variant only.
$edit = [
'label' => 'Style One',
'breakpoint_group' => 'responsive_image_test_module',
'fallback_image_style' => 'thumbnail',
'keyed_styles[responsive_image_test_module.mobile][1x][image_mapping_type]' => 'image_style',
'keyed_styles[responsive_image_test_module.mobile][1x][image_style]' => 'thumbnail',
'keyed_styles[responsive_image_test_module.narrow][1x][image_mapping_type]' => 'sizes',
// Ensure the Sizes field allows long values.
'keyed_styles[responsive_image_test_module.narrow][1x][sizes]' => '(min-resolution: 192dpi) and (min-width: 170px) 386px, (min-width: 170px) 193px, (min-width: 768px) 18vw, (min-width: 480px) 30vw, 48vw',
'keyed_styles[responsive_image_test_module.narrow][1x][sizes_image_styles][large]' => 'large',
'keyed_styles[responsive_image_test_module.narrow][1x][sizes_image_styles][medium]' => 'medium',
'keyed_styles[responsive_image_test_module.wide][1x][image_mapping_type]' => 'image_style',
'keyed_styles[responsive_image_test_module.wide][1x][image_style]' => 'large',
];
$this->drupalGet('admin/config/media/responsive-image-style/style_one');
$this->submitForm($edit, 'Save');
$this->drupalGet('admin/config/media/responsive-image-style/style_one');
// Check the mapping for multipliers 1x and 2x for the mobile breakpoint.
$this->assertSession()->fieldValueEquals('keyed_styles[responsive_image_test_module.mobile][1x][image_style]', 'thumbnail');
$this->assertSession()->fieldValueEquals('keyed_styles[responsive_image_test_module.mobile][1x][image_mapping_type]', 'image_style');
$this->assertSession()->fieldValueEquals('keyed_styles[responsive_image_test_module.mobile][2x][image_mapping_type]', '_none');
// Check the mapping for multipliers 1x and 2x for the narrow breakpoint.
$this->assertSession()->fieldValueEquals('keyed_styles[responsive_image_test_module.narrow][1x][image_mapping_type]', 'sizes');
$this->assertSession()->fieldValueEquals('keyed_styles[responsive_image_test_module.narrow][1x][sizes]', '(min-resolution: 192dpi) and (min-width: 170px) 386px, (min-width: 170px) 193px, (min-width: 768px) 18vw, (min-width: 480px) 30vw, 48vw');
$this->assertSession()->checkboxChecked('edit-keyed-styles-responsive-image-test-modulenarrow-1x-sizes-image-styles-large');
$this->assertSession()->checkboxChecked('edit-keyed-styles-responsive-image-test-modulenarrow-1x-sizes-image-styles-medium');
$this->assertSession()->checkboxNotChecked('edit-keyed-styles-responsive-image-test-modulenarrow-1x-sizes-image-styles-thumbnail');
$this->assertSession()->fieldValueEquals('keyed_styles[responsive_image_test_module.narrow][2x][image_mapping_type]', '_none');
// Check the mapping for multipliers 1x and 2x for the wide breakpoint.
$this->assertSession()->fieldValueEquals('keyed_styles[responsive_image_test_module.wide][1x][image_style]', 'large');
$this->assertSession()->fieldValueEquals('keyed_styles[responsive_image_test_module.wide][1x][image_mapping_type]', 'image_style');
$this->assertSession()->fieldValueEquals('keyed_styles[responsive_image_test_module.wide][2x][image_mapping_type]', '_none');
// Delete the style.
$this->drupalGet('admin/config/media/responsive-image-style/style_one/delete');
$this->submitForm([], 'Delete');
$this->drupalGet('admin/config/media/responsive-image-style');
$this->assertSession()->pageTextContains('There are no responsive image styles yet.');
}
}

View File

@ -0,0 +1,579 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\responsive_image\Functional;
use Drupal\image\Entity\ImageStyle;
use Drupal\image\ImageStyleInterface;
use Drupal\node\Entity\Node;
use Drupal\file\Entity\File;
use Drupal\responsive_image\Plugin\Field\FieldFormatter\ResponsiveImageFormatter;
use Drupal\responsive_image\Entity\ResponsiveImageStyle;
use Drupal\responsive_image\ResponsiveImageStyleInterface;
use Drupal\Tests\image\Functional\ImageFieldTestBase;
use Drupal\Tests\TestFileCreationTrait;
use Drupal\user\RoleInterface;
/**
* Tests responsive image display formatter.
*
* @group responsive_image
*/
class ResponsiveImageFieldDisplayTest extends ImageFieldTestBase {
use TestFileCreationTrait;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Responsive image style entity instance we test with.
*
* @var \Drupal\responsive_image\Entity\ResponsiveImageStyle
*/
protected $responsiveImgStyle;
/**
* The file URL generator.
*
* @var \Drupal\Core\File\FileUrlGeneratorInterface
*/
protected $fileUrlGenerator;
/**
* {@inheritdoc}
*/
protected static $modules = [
'field_ui',
'responsive_image',
'responsive_image_test_module',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->fileUrlGenerator = $this->container->get('file_url_generator');
// Create user.
$this->adminUser = $this->drupalCreateUser([
'administer responsive images',
'access content',
'access administration pages',
'administer site configuration',
'administer content types',
'administer node display',
'administer nodes',
'create article content',
'edit any article content',
'delete any article content',
'administer image styles',
]);
$this->drupalLogin($this->adminUser);
// Add responsive image style.
$this->responsiveImgStyle = ResponsiveImageStyle::create([
'id' => 'style_one',
'label' => 'Style One',
'breakpoint_group' => 'responsive_image_test_module',
'fallback_image_style' => 'large',
]);
}
/**
* Tests responsive image formatters on node display for public files.
*/
public function testResponsiveImageFieldFormattersPublic(): void {
$this->addTestImageStyleMappings();
$this->doTestResponsiveImageFieldFormatters('public');
}
/**
* Tests responsive image formatters on node display for private files.
*/
public function testResponsiveImageFieldFormattersPrivate(): void {
$this->addTestImageStyleMappings();
// Remove access content permission from anonymous users.
user_role_change_permissions(RoleInterface::ANONYMOUS_ID, ['access content' => FALSE]);
$this->doTestResponsiveImageFieldFormatters('private');
}
/**
* Tests responsive image formatters when image style is empty.
*/
public function testResponsiveImageFieldFormattersEmptyStyle(): void {
$this->addTestImageStyleMappings(TRUE);
$this->doTestResponsiveImageFieldFormatters('public', TRUE);
}
/**
* Add image style mappings to the responsive image style entity.
*
* @param bool $empty_styles
* If true, the image style mappings will get empty image styles.
*/
protected function addTestImageStyleMappings($empty_styles = FALSE): void {
if ($empty_styles) {
$this->responsiveImgStyle
->addImageStyleMapping('responsive_image_test_module.mobile', '1x', [
'image_mapping_type' => 'image_style',
'image_mapping' => '',
])
->addImageStyleMapping('responsive_image_test_module.narrow', '1x', [
'image_mapping_type' => 'sizes',
'image_mapping' => [
'sizes' => '(min-width: 700px) 700px, 100vw',
'sizes_image_styles' => [],
],
])
->addImageStyleMapping('responsive_image_test_module.wide', '1x', [
'image_mapping_type' => 'image_style',
'image_mapping' => '',
])
->save();
}
else {
$this->responsiveImgStyle
// Test the output of an empty image.
->addImageStyleMapping('responsive_image_test_module.mobile', '1x', [
'image_mapping_type' => 'image_style',
'image_mapping' => ResponsiveImageStyleInterface::EMPTY_IMAGE,
])
// Test the output with a 1.5x multiplier.
->addImageStyleMapping('responsive_image_test_module.mobile', '1.5x', [
'image_mapping_type' => 'image_style',
'image_mapping' => 'thumbnail',
])
// Test the output of the 'sizes' attribute.
->addImageStyleMapping('responsive_image_test_module.narrow', '1x', [
'image_mapping_type' => 'sizes',
'image_mapping' => [
'sizes' => '(min-width: 700px) 700px, 100vw',
'sizes_image_styles' => [
'large',
'medium',
],
],
])
// Test the normal output of mapping to an image style.
->addImageStyleMapping('responsive_image_test_module.wide', '1x', [
'image_mapping_type' => 'image_style',
'image_mapping' => 'large',
])
// Test the output of the original image.
->addImageStyleMapping('responsive_image_test_module.wide', '3x', [
'image_mapping_type' => 'image_style',
'image_mapping' => ResponsiveImageStyleInterface::ORIGINAL_IMAGE,
])
->save();
}
}
/**
* Tests responsive image formatters on node display.
*
* If the empty styles param is set, then the function only tests for the
* fallback image style (large).
*
* @param string $scheme
* File scheme to use.
* @param bool $empty_styles
* If true, use an empty string for image style names.
* Defaults to false.
*/
protected function doTestResponsiveImageFieldFormatters($scheme, $empty_styles = FALSE): void {
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = $this->container->get('renderer');
$node_storage = $this->container->get('entity_type.manager')->getStorage('node');
$field_name = $this->randomMachineName();
$this->createImageField($field_name, 'node', 'article', ['uri_scheme' => $scheme]);
// Create a new node with an image attached. Make sure we use a large image
// so the scale effects of the image styles always have an effect.
$test_image = current($this->getTestFiles('image', 39325));
// Create alt text for the image.
$alt = $this->randomMachineName();
$nid = $this->uploadNodeImage($test_image, $field_name, 'article', $alt);
$node = $node_storage->load($nid);
// Test that the default formatter is being used.
$image_uri = File::load($node->{$field_name}->target_id)->getFileUri();
$image = [
'#theme' => 'image',
'#uri' => $image_uri,
'#width' => 360,
'#height' => 240,
'#alt' => $alt,
'#attributes' => ['loading' => 'lazy'],
];
$default_output = str_replace("\n", '', (string) $renderer->renderRoot($image));
$this->assertSession()->responseContains($default_output);
// Test field not being configured. This should not cause a fatal error.
$display_options = [
'type' => 'responsive_image_test',
'settings' => ResponsiveImageFormatter::defaultSettings(),
];
$display = $this->container->get('entity_type.manager')
->getStorage('entity_view_display')
->load('node.article.default');
if (!$display) {
$values = [
'targetEntityType' => 'node',
'bundle' => 'article',
'mode' => 'default',
'status' => TRUE,
];
$display = $this->container->get('entity_type.manager')->getStorage('entity_view_display')->create($values);
}
$display->setComponent($field_name, $display_options)->save();
$this->drupalGet('node/' . $nid);
// Test theme function for responsive image, but using the test formatter.
$display_options = [
'type' => 'responsive_image_test',
'settings' => [
'image_link' => 'file',
'responsive_image_style' => 'style_one',
],
];
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
$display_repository = \Drupal::service('entity_display.repository');
$display = $display_repository->getViewDisplay('node', 'article');
$display->setComponent($field_name, $display_options)
->save();
$this->drupalGet('node/' . $nid);
// Use the responsive image formatter linked to file formatter.
$display_options = [
'type' => 'responsive_image',
'settings' => [
'image_link' => 'file',
'responsive_image_style' => 'style_one',
],
];
$display = $display_repository->getViewDisplay('node', 'article');
$display->setComponent($field_name, $display_options)
->save();
$this->drupalGet('node/' . $nid);
// No image style cache tag should be found.
$this->assertSession()->responseHeaderNotContains('X-Drupal-Cache-Tags', 'image_style:');
$this->assertSession()->responseMatches('/<a(.*?)href="' . preg_quote($this->fileUrlGenerator->generateString($image_uri), '/') . '"(.*?)>\s*<picture/');
// Verify that the image can be downloaded.
$this->assertEquals(file_get_contents($test_image->uri), $this->drupalGet($this->fileUrlGenerator->generateAbsoluteString($image_uri)), 'File was downloaded successfully.');
if ($scheme == 'private') {
// Only verify HTTP headers when using private scheme and the headers are
// sent by Drupal.
$this->assertSession()->responseHeaderEquals('Content-Type', 'image/png');
$this->assertSession()->responseHeaderContains('Cache-Control', 'private');
// Log out and ensure the file cannot be accessed.
$this->drupalLogout();
$this->drupalGet($this->fileUrlGenerator->generateAbsoluteString($image_uri));
$this->assertSession()->statusCodeEquals(403);
// Log in again.
$this->drupalLogin($this->adminUser);
}
// Use the responsive image formatter with a responsive image style.
$display_options['settings']['responsive_image_style'] = 'style_one';
$display_options['settings']['image_link'] = '';
$display->setComponent($field_name, $display_options)
->save();
// Create a derivative so at least one MIME type will be known.
$large_style = ImageStyle::load('large');
$large_style->createDerivative($image_uri, $large_style->buildUri($image_uri));
// Output should contain all image styles and all breakpoints.
$this->drupalGet('node/' . $nid);
if (!$empty_styles) {
$this->assertSession()->responseContains('/styles/medium/');
// Assert the empty image is present.
$this->assertSession()->responseContains('data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==');
$thumbnail_style = ImageStyle::load('thumbnail');
// Assert the output of the 'srcset' attribute (small multipliers first).
$this->assertSession()->responseContains('data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== 1x, ' . $this->fileUrlGenerator->transformRelative($thumbnail_style->buildUrl($image_uri)) . ' 1.5x');
$this->assertSession()->responseContains('/styles/medium/');
// Assert the output of the original image.
$this->assertSession()->responseContains($this->fileUrlGenerator->generateString($image_uri) . ' 3x');
// Assert the output of the breakpoints.
$this->assertSession()->responseContains('media="(min-width: 0px)"');
$this->assertSession()->responseContains('media="(min-width: 560px)"');
// Assert the output of the 'sizes' attribute.
$this->assertSession()->responseContains('sizes="(min-width: 700px) 700px, 100vw"');
$this->assertSession()->responseMatches('/media="\(min-width: 560px\)".+?sizes="\(min-width: 700px\) 700px, 100vw"/');
// Assert the output of the 'srcset' attribute (small images first).
$medium_style = ImageStyle::load('medium');
$this->assertSession()->responseContains($this->fileUrlGenerator->transformRelative($medium_style->buildUrl($image_uri)) . ' 220w, ' . $this->fileUrlGenerator->transformRelative($large_style->buildUrl($image_uri)) . ' 360w');
$this->assertSession()->responseContains('media="(min-width: 851px)"');
// Assert the output of the 'width' attribute.
$this->assertSession()->responseContains('width="360"');
// Assert the output of the 'height' attribute.
$this->assertSession()->responseContains('height="240"');
$this->assertSession()->responseContains('loading="lazy"');
}
$this->assertSession()->responseContains('/styles/large/');
$this->assertSession()->responseHeaderContains('X-Drupal-Cache-Tags', 'config:responsive_image.styles.style_one');
if (!$empty_styles) {
$this->assertSession()->responseHeaderContains('X-Drupal-Cache-Tags', 'config:image.style.medium');
$this->assertSession()->responseHeaderContains('X-Drupal-Cache-Tags', 'config:image.style.thumbnail');
$this->assertSession()->responseContains('type="image/avif"');
}
$this->assertSession()->responseHeaderContains('X-Drupal-Cache-Tags', 'config:image.style.large');
// Test the fallback image style. Copy the source image:
$fallback_image = $image;
// Set the fallback image style uri:
$fallback_image['#uri'] = $this->fileUrlGenerator->transformRelative($large_style->buildUrl($image_uri));
// The image.html.twig template has a newline after the <img> tag but
// responsive-image.html.twig doesn't have one after the fallback image, so
// we remove it here.
$default_output = trim((string) $renderer->renderRoot($fallback_image));
$this->assertSession()->responseContains($default_output);
if ($scheme == 'private') {
// Log out and ensure the file cannot be accessed.
$this->drupalLogout();
$this->drupalGet($large_style->buildUrl($image_uri));
$this->assertSession()->statusCodeEquals(403);
$this->assertSession()->responseHeaderNotMatches('X-Drupal-Cache-Tags', '/ image_style\:/');
}
}
/**
* Tests responsive image formatters on node display linked to the file.
*/
public function testResponsiveImageFieldFormattersLinkToFile(): void {
$this->addTestImageStyleMappings();
$this->assertResponsiveImageFieldFormattersLink('file');
}
/**
* Tests responsive image formatters on node display linked to the node.
*/
public function testResponsiveImageFieldFormattersLinkToNode(): void {
$this->addTestImageStyleMappings();
$this->assertResponsiveImageFieldFormattersLink('content');
}
/**
* Tests responsive image formatter on node display with an empty media query.
*/
public function testResponsiveImageFieldFormattersEmptyMediaQuery(): void {
$this->responsiveImgStyle
// Test the output of an empty media query.
->addImageStyleMapping('responsive_image_test_module.empty', '1x', [
'image_mapping_type' => 'image_style',
'image_mapping' => ResponsiveImageStyleInterface::EMPTY_IMAGE,
])
// Test the output with a 1.5x multiplier.
->addImageStyleMapping('responsive_image_test_module.mobile', '1x', [
'image_mapping_type' => 'image_style',
'image_mapping' => 'thumbnail',
])
->save();
$node_storage = $this->container->get('entity_type.manager')->getStorage('node');
$field_name = $this->randomMachineName();
$this->createImageField($field_name, 'node', 'article', ['uri_scheme' => 'public']);
// Create a new node with an image attached.
$test_image = current($this->getTestFiles('image'));
$nid = $this->uploadNodeImage($test_image, $field_name, 'article', $this->randomMachineName());
// Use the responsive image formatter linked to file formatter.
$display_options = [
'type' => 'responsive_image',
'settings' => [
'image_link' => '',
'responsive_image_style' => 'style_one',
],
];
$display = \Drupal::service('entity_display.repository')
->getViewDisplay('node', 'article');
$display->setComponent($field_name, $display_options)
->save();
// View the node.
$this->drupalGet('node/' . $nid);
// Assert an empty media attribute is not output.
$this->assertSession()->responseNotMatches('@srcset="data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== 1x".+?media=".+?/><source@');
// Assert the media attribute is present if it has a value.
$thumbnail_style = ImageStyle::load('thumbnail');
$node = $node_storage->load($nid);
$image_uri = File::load($node->{$field_name}->target_id)->getFileUri();
$this->assertSession()->responseMatches('/srcset="' . preg_quote($this->fileUrlGenerator->transformRelative($thumbnail_style->buildUrl($image_uri)), '/') . ' 1x".+?media="\(min-width: 0px\)"/');
}
/**
* Tests responsive image formatter on node display with one and two sources.
*/
public function testResponsiveImageFieldFormattersMultipleSources(): void {
// Setup known image style sizes so the test can assert on known sizes.
$large_style = ImageStyle::load('large');
assert($large_style instanceof ImageStyleInterface);
$large_style->addImageEffect([
'id' => 'image_resize',
'weight' => 0,
'data' => [
'width' => '480',
'height' => '480',
],
]);
$large_style->save();
$medium_style = ImageStyle::load('medium');
assert($medium_style instanceof ImageStyleInterface);
$medium_style->addImageEffect([
'id' => 'image_resize',
'weight' => 0,
'data' => [
'width' => '220',
'height' => '220',
],
]);
$medium_style->save();
$this->responsiveImgStyle
// Test the output of an empty media query.
->addImageStyleMapping('responsive_image_test_module.empty', '1x', [
'image_mapping_type' => 'image_style',
'image_mapping' => $medium_style->id(),
])
->addImageStyleMapping('responsive_image_test_module.empty', '1.5x', [
'image_mapping_type' => 'image_style',
'image_mapping' => $large_style->id(),
])
->addImageStyleMapping('responsive_image_test_module.empty', '2x', [
'image_mapping_type' => 'image_style',
'image_mapping' => $large_style->id(),
])
->save();
$node_storage = $this->container->get('entity_type.manager')->getStorage('node');
$field_name = $this->randomMachineName();
$this->createImageField($field_name, 'node', 'article', ['uri_scheme' => 'public']);
// Create a new node with an image attached.
$test_image = current($this->getTestFiles('image'));
$nid = $this->uploadNodeImage($test_image, $field_name, 'article', $this->randomMachineName());
// Use the responsive image formatter linked to file formatter.
$display_options = [
'type' => 'responsive_image',
'settings' => [
'image_link' => '',
'responsive_image_style' => 'style_one',
'image_loading' => [
// Test the image loading default option can be overridden.
'attribute' => 'eager',
],
],
];
$display = \Drupal::service('entity_display.repository')
->getViewDisplay('node', 'article');
$display->setComponent($field_name, $display_options)
->save();
// View the node.
$this->drupalGet('node/' . $nid);
// Assert the img tag has medium and large images and fallback dimensions
// from the large image style are used.
$node = $node_storage->load($nid);
$image_uri = File::load($node->{$field_name}->target_id)->getFileUri();
$medium_transform_url = $this->fileUrlGenerator->transformRelative($medium_style->buildUrl($image_uri));
$large_transform_url = $this->fileUrlGenerator->transformRelative($large_style->buildUrl($image_uri));
$this->assertSession()->responseMatches('/<img loading="eager" srcset="' . \preg_quote($medium_transform_url, '/') . ' 1x, ' . \preg_quote($large_transform_url, '/') . ' 1.5x, ' . \preg_quote($large_transform_url, '/') . ' 2x" width="480" height="480" src="' . \preg_quote($large_transform_url, '/') . '" alt="\w+" \/>/');
$this->responsiveImgStyle
// Test the output of an empty media query.
->addImageStyleMapping('responsive_image_test_module.wide', '1x', [
'image_mapping_type' => 'image_style',
'image_mapping' => $large_style->id(),
])
->save();
// Assert the picture tag has source tags that include dimensions.
$this->drupalGet('node/' . $nid);
$this->assertSession()->responseMatches('/<picture>\s+<source srcset="' . \preg_quote($large_transform_url, '/') . ' 1x" media="\(min-width: 851px\)" type="image\/avif" width="480" height="480"\/>\s+<source srcset="' . \preg_quote($medium_transform_url, '/') . ' 1x, ' . \preg_quote($large_transform_url, '/') . ' 1.5x, ' . \preg_quote($large_transform_url, '/') . ' 2x" type="image\/avif" width="220" height="220"\/>\s+<img loading="eager" width="480" height="480" src="' . \preg_quote($large_transform_url, '/') . '" alt="\w+" \/>\s+<\/picture>/');
}
/**
* Tests responsive image formatters linked to the file or node.
*
* @param string $link_type
* The link type to test. Either 'file' or 'content'.
*/
private function assertResponsiveImageFieldFormattersLink(string $link_type): void {
$field_name = $this->randomMachineName();
$field_settings = ['alt_field_required' => 0];
$this->createImageField($field_name, 'node', 'article', ['uri_scheme' => 'public'], $field_settings);
// Create a new node with an image attached.
$test_image = current($this->getTestFiles('image'));
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
$display_repository = \Drupal::service('entity_display.repository');
// Test the image linked to file formatter.
$display_options = [
'type' => 'responsive_image',
'settings' => [
'image_link' => $link_type,
'responsive_image_style' => 'style_one',
],
];
$display_repository->getViewDisplay('node', 'article')
->setComponent($field_name, $display_options)
->save();
// Ensure that preview works.
$this->previewNodeImage($test_image, $field_name, 'article');
// Look for a picture tag in the preview output
$this->assertSession()->responseMatches('/picture/');
$nid = $this->uploadNodeImage($test_image, $field_name, 'article');
$this->container->get('entity_type.manager')->getStorage('node')->resetCache([$nid]);
$node = Node::load($nid);
// Use the responsive image formatter linked to file formatter.
$display_options = [
'type' => 'responsive_image',
'settings' => [
'image_link' => $link_type,
'responsive_image_style' => 'style_one',
],
];
$display_repository->getViewDisplay('node', 'article')
->setComponent($field_name, $display_options)
->save();
// Create a derivative so at least one MIME type will be known.
$large_style = ImageStyle::load('large');
$image_uri = File::load($node->{$field_name}->target_id)->getFileUri();
$large_style->createDerivative($image_uri, $large_style->buildUri($image_uri));
// Output should contain all image styles and all breakpoints.
$this->drupalGet('node/' . $nid);
switch ($link_type) {
case 'file':
// Make sure the link to the file is present.
$this->assertSession()->responseMatches('/<a(.*?)href="' . preg_quote($this->fileUrlGenerator->generateString($image_uri), '/') . '"(.*?)>\s*<picture/');
break;
case 'content':
// Make sure the link to the node is present.
$this->assertSession()->responseMatches('/<a(.*?)href="' . preg_quote($node->toUrl()->toString(), '/') . '"(.*?)>\s*<picture/');
break;
}
}
}

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\responsive_image\Functional\Rest;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
/**
* @group rest
*/
class ResponsiveImageStyleJsonAnonTest extends ResponsiveImageStyleResourceTestBase {
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\responsive_image\Functional\Rest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
/**
* @group rest
*/
class ResponsiveImageStyleJsonBasicAuthTest extends ResponsiveImageStyleResourceTestBase {
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\responsive_image\Functional\Rest;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
/**
* @group rest
*/
class ResponsiveImageStyleJsonCookieTest extends ResponsiveImageStyleResourceTestBase {
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,132 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\responsive_image\Functional\Rest;
use Drupal\responsive_image\Entity\ResponsiveImageStyle;
use Drupal\Tests\rest\Functional\EntityResource\ConfigEntityResourceTestBase;
/**
* Resource test base for ResponsiveImageStyle entity.
*/
abstract class ResponsiveImageStyleResourceTestBase extends ConfigEntityResourceTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['responsive_image'];
/**
* {@inheritdoc}
*/
protected static $entityTypeId = 'responsive_image_style';
/**
* The ResponsiveImageStyle entity.
*
* @var \Drupal\responsive_image\ResponsiveImageStyleInterface
*/
protected $entity;
/**
* The effect UUID.
*
* @var string
*/
protected $effectUuid;
/**
* {@inheritdoc}
*/
protected function setUpAuthorization($method) {
$this->grantPermissionsToTestedRole(['administer responsive images']);
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
// Create a "Camelids" responsive image style.
$camelids = ResponsiveImageStyle::create([
'id' => 'camelids',
'label' => 'Camelids',
]);
$camelids->setBreakpointGroup('test_group');
$camelids->setFallbackImageStyle('fallback');
$camelids->addImageStyleMapping('test_breakpoint', '1x', [
'image_mapping_type' => 'image_style',
'image_mapping' => 'small',
]);
$camelids->addImageStyleMapping('test_breakpoint', '2x', [
'image_mapping_type' => 'sizes',
'image_mapping' => [
'sizes' => '(min-width:700px) 700px, 100vw',
'sizes_image_styles' => [
'medium' => 'medium',
'large' => 'large',
],
],
]);
$camelids->save();
return $camelids;
}
/**
* {@inheritdoc}
*/
protected function getExpectedNormalizedEntity() {
return [
'breakpoint_group' => 'test_group',
'dependencies' => [
'config' => [
'image.style.large',
'image.style.medium',
],
],
'fallback_image_style' => 'fallback',
'id' => 'camelids',
'image_style_mappings' => [
0 => [
'breakpoint_id' => 'test_breakpoint',
'image_mapping' => 'small',
'image_mapping_type' => 'image_style',
'multiplier' => '1x',
],
1 => [
'breakpoint_id' => 'test_breakpoint',
'image_mapping' => [
'sizes' => '(min-width:700px) 700px, 100vw',
'sizes_image_styles' => [
'large' => 'large',
'medium' => 'medium',
],
],
'image_mapping_type' => 'sizes',
'multiplier' => '2x',
],
],
'label' => 'Camelids',
'langcode' => 'en',
'status' => TRUE,
'uuid' => $this->entity->uuid(),
];
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPostEntity() {
// @todo Update in https://www.drupal.org/node/2300677.
return [];
}
/**
* {@inheritdoc}
*/
protected function getExpectedUnauthorizedAccessMessage($method) {
return "The 'administer responsive images' permission is required.";
}
}

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\responsive_image\Functional\Rest;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class ResponsiveImageStyleXmlAnonTest extends ResponsiveImageStyleResourceTestBase {
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\responsive_image\Functional\Rest;
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class ResponsiveImageStyleXmlBasicAuthTest extends ResponsiveImageStyleResourceTestBase {
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\responsive_image\Functional\Rest;
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
/**
* @group rest
*/
class ResponsiveImageStyleXmlCookieTest extends ResponsiveImageStyleResourceTestBase {
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,116 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\responsive_image\Functional;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\responsive_image\Entity\ResponsiveImageStyle;
use Drupal\Tests\views\Functional\ViewTestBase;
/**
* Tests the integration of responsive image with Views.
*
* @group responsive_image
*/
class ViewsIntegrationTest extends ViewTestBase {
/**
* The responsive image style ID to use.
*/
const RESPONSIVE_IMAGE_STYLE_ID = 'responsive_image_style_id';
/**
* {@inheritdoc}
*/
protected static $modules = [
'views',
'views_ui',
'responsive_image',
'field',
'image',
'file',
'entity_test',
'breakpoint',
'responsive_image_test_module',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* The test views to enable.
*
* @var string[]
*/
public static $testViews = ['entity_test_row'];
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE, $modules = ['views_test_config']): void {
parent::setUp($import_test_views, $modules);
$this->enableViewsTestModule();
// Create a responsive image style.
$responsive_image_style = ResponsiveImageStyle::create([
'id' => self::RESPONSIVE_IMAGE_STYLE_ID,
'label' => 'Foo',
'breakpoint_group' => 'responsive_image_test_module',
]);
// Create an image field to be used with a responsive image formatter.
FieldStorageConfig::create([
'type' => 'image',
'entity_type' => 'entity_test',
'field_name' => 'bar',
])->save();
FieldConfig::create([
'entity_type' => 'entity_test',
'bundle' => 'entity_test',
'field_name' => 'bar',
])->save();
$responsive_image_style
->addImageStyleMapping('responsive_image_test_module.mobile', '1x', [
'image_mapping_type' => 'image_style',
'image_mapping' => 'thumbnail',
])
->addImageStyleMapping('responsive_image_test_module.narrow', '1x', [
'image_mapping_type' => 'image_style',
'image_mapping' => 'medium',
])
// Test the normal output of mapping to an image style.
->addImageStyleMapping('responsive_image_test_module.wide', '1x', [
'image_mapping_type' => 'image_style',
'image_mapping' => 'large',
])
->save();
$admin_user = $this->drupalCreateUser(['administer views']);
$this->drupalLogin($admin_user);
}
/**
* Tests integration with Views.
*/
public function testViewsAddResponsiveImageField(): void {
// Add the image field to the View.
$this->drupalGet('admin/structure/views/nojs/add-handler/entity_test_row/default/field');
$this->drupalGet('admin/structure/views/nojs/add-handler/entity_test_row/default/field');
$this->submitForm(['name[entity_test__bar.bar]' => TRUE], 'Add and configure field');
// Set the formatter to 'Responsive image'.
$this->submitForm(['options[type]' => 'responsive_image'], 'Apply');
$this->assertSession()
->responseContains('Responsive image style field is required.');
$this->submitForm(['options[settings][responsive_image_style]' => self::RESPONSIVE_IMAGE_STYLE_ID], 'Apply');
$this->drupalGet('admin/structure/views/nojs/handler/entity_test_row/default/field/bar');
// Make sure the selected value is set.
$this->assertSession()
->fieldValueEquals('options[settings][responsive_image_style]', self::RESPONSIVE_IMAGE_STYLE_ID);
}
}

View File

@ -0,0 +1,159 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\responsive_image\FunctionalJavascript;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
use Drupal\responsive_image\Entity\ResponsiveImageStyle;
use Drupal\Tests\field_ui\Traits\FieldUiJSTestTrait;
/**
* Tests the responsive image field UI.
*
* @group responsive_image
*/
class ResponsiveImageFieldUiTest extends WebDriverTestBase {
use FieldUiJSTestTrait;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $modules = [
'node',
'field_ui',
'image',
'responsive_image',
'responsive_image_test_module',
'block',
];
/**
* The content type id.
*
* @var string
*/
protected string $type;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->drupalPlaceBlock('system_breadcrumb_block');
$this->drupalPlaceBlock('local_actions_block');
// Create a test user.
$admin_user = $this->drupalCreateUser([
'access content',
'administer content types',
'administer node fields',
'administer node form display',
'administer node display',
'bypass node access',
]);
$this->drupalLogin($admin_user);
// Create content type, with underscores.
$type_name = $this->randomMachineName(8) . '_test';
$type = $this->drupalCreateContentType([
'name' => $type_name,
'type' => $type_name,
]);
$this->type = $type->id();
}
/**
* Tests formatter settings.
*/
public function testResponsiveImageFormatterUi(): void {
$manage = 'admin/structure/types/manage/' . $this->type;
$manage_display = $manage . '/display';
/** @var \Drupal\FunctionalJavascriptTests\JSWebAssert $assert_session */
$assert_session = $this->assertSession();
$this->fieldUIAddNewFieldJS('admin/structure/types/manage/' . $this->type, 'image', 'Image', 'image');
// Display the "Manage display" page.
$this->drupalGet($manage_display);
// Change the formatter and check that the summary is updated.
$page = $this->getSession()->getPage();
$field_image_type = $page->findField('fields[field_image][type]');
$field_image_type->setValue('responsive_image');
$summary_text = $assert_session->waitForElement('xpath', $this->cssSelectToXpath('#field-image .ajax-new-content .field-plugin-summary'));
$this->assertEquals('Select a responsive image style. Loading attribute: lazy', $summary_text->getText());
$page->pressButton('Save');
$assert_session->responseContains("Select a responsive image style.");
// Create responsive image styles.
$responsive_image_style = ResponsiveImageStyle::create([
'id' => 'style_one',
'label' => 'Style One',
'breakpoint_group' => 'responsive_image_test_module',
'fallback_image_style' => 'thumbnail',
]);
$responsive_image_style
->addImageStyleMapping('responsive_image_test_module.mobile', '1x', [
'image_mapping_type' => 'image_style',
'image_mapping' => 'thumbnail',
])
->addImageStyleMapping('responsive_image_test_module.narrow', '1x', [
'image_mapping_type' => 'image_style',
'image_mapping' => 'medium',
])
// Test the normal output of mapping to an image style.
->addImageStyleMapping('responsive_image_test_module.wide', '1x', [
'image_mapping_type' => 'image_style',
'image_mapping' => 'large',
])
->save();
\Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
// Refresh the page.
$this->drupalGet($manage_display);
$assert_session->responseContains("Select a responsive image style.");
// Click on the formatter settings button to open the formatter settings
// form.
$field_image_type = $page->findField('fields[field_image][type]');
$field_image_type->setValue('responsive_image');
$page->find('css', '#edit-fields-field-image-settings-edit')->click();
$assert_session->waitForField('fields[field_image][settings_edit_form][settings][responsive_image_style]');
// Assert that the correct fields are present.
$fieldnames = [
'fields[field_image][settings_edit_form][settings][responsive_image_style]',
'fields[field_image][settings_edit_form][settings][image_link]',
];
foreach ($fieldnames as $fieldname) {
$assert_session->fieldExists($fieldname);
}
$page->findField('fields[field_image][settings_edit_form][settings][responsive_image_style]')->setValue('style_one');
$page->findField('fields[field_image][settings_edit_form][settings][image_link]')->setValue('content');
// Save the form to save the settings.
$page->pressButton('Save');
$this->assertTrue($assert_session->waitForText('Responsive image style: Style One'));
$this->assertTrue($assert_session->waitForText('Linked to content'));
$page->find('css', '#edit-fields-field-image-settings-edit')->click();
$assert_session->waitForField('fields[field_image][settings_edit_form][settings][responsive_image_style]');
$page->findField('fields[field_image][settings_edit_form][settings][image_link]')->setValue('file');
// Save the form to save the settings.
$page->pressButton('Save');
$this->assertTrue($assert_session->waitForText('Responsive image style: Style One'));
$this->assertTrue($assert_session->waitForText('Linked to file'));
}
}

View File

@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\responsive_image\Kernel\Migrate\d7;
use Drupal\responsive_image\Entity\ResponsiveImageStyle;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
/**
* Tests migration of responsive image styles.
*
* @group responsive_image
*/
class MigrateResponsiveImageStylesTest extends MigrateDrupal7TestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['responsive_image', 'breakpoint', 'image'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
// Ensure the 'picture' module is enabled in the source.
$this->sourceDatabase->update('system')
->condition('name', 'picture')
->fields(['status' => 1])
->execute();
$this->executeMigrations(['d7_image_styles', 'd7_responsive_image_styles']);
}
/**
* Tests the Drupal 7 to Drupal 8 responsive image styles migration.
*/
public function testResponsiveImageStyles(): void {
$expected_image_style_mappings = [
[
'image_mapping_type' => 'image_style',
'image_mapping' => 'custom_image_style_1',
'breakpoint_id' => 'responsive_image.computer',
'multiplier' => 'multiplier_1',
],
[
'image_mapping_type' => 'sizes',
'image_mapping' => [
'sizes' => '2',
'sizes_image_styles' => [
'custom_image_style_1',
'custom_image_style_2',
],
],
'breakpoint_id' => 'responsive_image.computer',
'multiplier' => 'multiplier_2',
],
[
'image_mapping_type' => 'sizes',
'image_mapping' => [
'sizes' => '2',
'sizes_image_styles' => [
'custom_image_style_1',
'custom_image_style_2',
],
],
'breakpoint_id' => 'responsive_image.computertwo',
'multiplier' => 'multiplier_2',
],
];
$this->assertSame($expected_image_style_mappings, ResponsiveImageStyle::load('narrow')
->getImageStyleMappings());
}
}

View File

@ -0,0 +1,90 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\responsive_image\Kernel\Plugin\migrate\source\d7;
use Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase;
/**
* Tests D7 responsive image styles source plugin.
*
* @covers \Drupal\responsive_image\Plugin\migrate\source\d7\ResponsiveImageStyles
* @group image
*/
class ResponsiveImageStylesTest extends MigrateSqlSourceTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'migrate_drupal',
'responsive_image',
];
/**
* {@inheritdoc}
*/
public static function providerSource() {
$tests = [];
// The source data.
$tests[0]['source_data']['picture_mapping'] = [
[
'label' => 'Narrow',
'machine_name' => 'narrow',
'breakpoint_group' => 'responsive_image',
'mapping' => 'a:2:{s:38:"breakpoints.theme.my_theme_id.computer";a:3:{s:12:"multiplier_1";a:2:{s:12:"mapping_type";s:11:"image_style";s:11:"image_style";s:20:"custom_image_style_1";}s:12:"multiplier_2";a:3:{s:12:"mapping_type";s:5:"sizes";s:5:"sizes";i:2;s:18:"sizes_image_styles";a:2:{i:0;s:20:"custom_image_style_1";i:1;s:20:"custom_image_style_2";}}s:12:"multiplier_3";a:1:{s:12:"mapping_type";s:5:"_none";}}s:42:"breakpoints.theme.my_theme_id.computer_two";a:1:{s:12:"multiplier_2";a:3:{s:12:"mapping_type";s:5:"sizes";s:5:"sizes";i:2;s:18:"sizes_image_styles";a:2:{i:0;s:20:"custom_image_style_1";i:1;s:20:"custom_image_style_2";}}}}',
],
];
// The expected results.
$tests[0]['expected_data'] = [
[
'label' => 'Narrow',
'machine_name' => 'narrow',
'breakpoint_group' => 'responsive_image',
'mapping' => [
'breakpoints.theme.my_theme_id.computer' =>
[
'multiplier_1' =>
[
'mapping_type' => 'image_style',
'image_style' => 'custom_image_style_1',
],
'multiplier_2' =>
[
'mapping_type' => 'sizes',
'sizes' => 2,
'sizes_image_styles' =>
[
0 => 'custom_image_style_1',
1 => 'custom_image_style_2',
],
],
'multiplier_3' =>
[
'mapping_type' => '_none',
],
],
'breakpoints.theme.my_theme_id.computer_two' =>
[
'multiplier_2' =>
[
'mapping_type' => 'sizes',
'sizes' => 2,
'sizes_image_styles' =>
[
0 => 'custom_image_style_1',
1 => 'custom_image_style_2',
],
],
],
],
],
];
return $tests;
}
}

View File

@ -0,0 +1,91 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\responsive_image\Kernel;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\KernelTests\KernelTestBase;
use Drupal\responsive_image\Entity\ResponsiveImageStyle;
/**
* Tests the integration of responsive image with other components.
*
* @group responsive_image
*/
class ResponsiveImageIntegrationTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'responsive_image',
'field',
'image',
'file',
'entity_test',
'breakpoint',
'responsive_image_test_module',
'user',
];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('entity_test');
}
/**
* Tests integration with entity view display.
*/
public function testEntityViewDisplayDependency(): void {
// Create a responsive image style.
ResponsiveImageStyle::create([
'id' => 'foo',
'label' => 'Foo',
'breakpoint_group' => 'responsive_image_test_module',
])->save();
// Create an image field to be used with a responsive image formatter.
FieldStorageConfig::create([
'type' => 'image',
'entity_type' => 'entity_test',
'field_name' => 'bar',
])->save();
FieldConfig::create([
'entity_type' => 'entity_test',
'bundle' => 'entity_test',
'field_name' => 'bar',
])->save();
/** @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display */
$display = EntityViewDisplay::create([
'targetEntityType' => 'entity_test',
'bundle' => 'entity_test',
'mode' => 'default',
]);
$display->setComponent('bar', [
'type' => 'responsive_image',
'label' => 'hidden',
'settings' => ['responsive_image_style' => 'foo', 'image_link' => ''],
'third_party_settings' => [],
])->save();
// Check that the 'foo' field is on the display.
$this->assertNotNull($display = EntityViewDisplay::load('entity_test.entity_test.default'));
$this->assertNotEmpty($display->getComponent('bar'));
$this->assertArrayNotHasKey('bar', $display->get('hidden'));
// Delete the responsive image style.
ResponsiveImageStyle::load('foo')->delete();
// Check that the view display was not deleted.
$this->assertNotNull($display = EntityViewDisplay::load('entity_test.entity_test.default'));
// Check that the 'foo' field was disabled.
$this->assertNull($display->getComponent('bar'));
$this->assertArrayHasKey('bar', $display->get('hidden'));
}
}

View File

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\responsive_image\Kernel;
use Drupal\KernelTests\Core\Config\ConfigEntityValidationTestBase;
use Drupal\responsive_image\Entity\ResponsiveImageStyle;
/**
* Tests validation of responsive_image_style entities.
*
* @group responsive_image
*/
class ResponsiveImageStyleValidationTest extends ConfigEntityValidationTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['breakpoint', 'image', 'responsive_image'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->entity = ResponsiveImageStyle::create([
'id' => 'test',
'label' => 'Test',
]);
$this->entity->save();
}
}

View File

@ -0,0 +1,407 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\responsive_image\Unit;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\EntityTypeRepositoryInterface;
use Drupal\responsive_image\Entity\ResponsiveImageStyle;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\responsive_image\Entity\ResponsiveImageStyle
* @group block
*/
class ResponsiveImageStyleConfigEntityUnitTest extends UnitTestCase {
/**
* The entity type used for testing.
*
* @var \Drupal\Core\Entity\EntityTypeInterface|\PHPUnit\Framework\MockObject\MockObject
*/
protected $entityType;
/**
* The entity type manager used for testing.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface|\PHPUnit\Framework\MockObject\MockObject
*/
protected $entityTypeManager;
/**
* The breakpoint manager used for testing.
*
* @var \Drupal\breakpoint\BreakpointManagerInterface|\PHPUnit\Framework\MockObject\MockObject
*/
protected $breakpointManager;
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->entityType = $this->createMock('\Drupal\Core\Entity\EntityTypeInterface');
$this->entityType->expects($this->any())
->method('getProvider')
->willReturn('responsive_image');
$this->entityTypeManager = $this->createMock(EntityTypeManagerInterface::class);
$this->entityTypeManager->expects($this->any())
->method('getDefinition')
->with('responsive_image_style')
->willReturn($this->entityType);
$this->breakpointManager = $this->createMock('\Drupal\breakpoint\BreakpointManagerInterface');
$container = new ContainerBuilder();
$container->set('entity_type.manager', $this->entityTypeManager);
$container->set('breakpoint.manager', $this->breakpointManager);
\Drupal::setContainer($container);
}
/**
* @covers ::calculateDependencies
*/
public function testCalculateDependencies(): void {
// Set up image style loading mock.
$styles = [];
foreach (['fallback', 'small', 'medium', 'large'] as $style) {
$mock = $this->createMock('Drupal\Core\Config\Entity\ConfigEntityInterface');
$mock->expects($this->any())
->method('getConfigDependencyName')
->willReturn('image.style.' . $style);
$styles[$style] = $mock;
}
$storage = $this->createMock('\Drupal\Core\Config\Entity\ConfigEntityStorageInterface');
$storage->expects($this->any())
->method('loadMultiple')
->with(array_keys($styles))
->willReturn($styles);
$this->entityTypeManager->expects($this->any())
->method('getStorage')
->with('image_style')
->willReturn($storage);
$entity_type_repository = $this->createMock(EntityTypeRepositoryInterface::class);
$entity_type_repository->expects($this->any())
->method('getEntityTypeFromClass')
->with('Drupal\image\Entity\ImageStyle')
->willReturn('image_style');
$entity = new ResponsiveImageStyle(['breakpoint_group' => 'test_group']);
$entity->setBreakpointGroup('test_group');
$entity->setFallbackImageStyle('fallback');
$entity->addImageStyleMapping('test_breakpoint', '1x', [
'image_mapping_type' => 'image_style',
'image_mapping' => 'small',
]);
$entity->addImageStyleMapping('test_breakpoint', '2x', [
'image_mapping_type' => 'sizes',
'image_mapping' => [
'sizes' => '(min-width:700px) 700px, 100vw',
'sizes_image_styles' => [
'medium' => 'medium',
'large' => 'large',
],
],
]);
$this->breakpointManager->expects($this->any())
->method('getGroupProviders')
->with('test_group')
->willReturn(['olivero' => 'theme', 'toolbar' => 'module']);
\Drupal::getContainer()->set('entity_type.repository', $entity_type_repository);
$dependencies = $entity->calculateDependencies()->getDependencies();
$this->assertEquals(['toolbar'], $dependencies['module']);
$this->assertEquals(['olivero'], $dependencies['theme']);
$this->assertEquals(['image.style.fallback', 'image.style.large', 'image.style.medium', 'image.style.small'], $dependencies['config']);
}
/**
* @covers ::addImageStyleMapping
* @covers ::hasImageStyleMappings
*/
public function testHasImageStyleMappings(): void {
$entity = new ResponsiveImageStyle([]);
$this->assertFalse($entity->hasImageStyleMappings());
$entity->addImageStyleMapping('test_breakpoint', '1x', [
'image_mapping_type' => 'image_style',
'image_mapping' => '',
]);
$this->assertFalse($entity->hasImageStyleMappings());
$entity->removeImageStyleMappings();
$entity->addImageStyleMapping('test_breakpoint', '1x', [
'image_mapping_type' => 'sizes',
'image_mapping' => [
'sizes' => '(min-width:700px) 700px, 100vw',
'sizes_image_styles' => [],
],
]);
$this->assertFalse($entity->hasImageStyleMappings());
$entity->removeImageStyleMappings();
$entity->addImageStyleMapping('test_breakpoint', '1x', [
'image_mapping_type' => 'sizes',
'image_mapping' => [
'sizes' => '',
'sizes_image_styles' => [
'large' => 'large',
],
],
]);
$this->assertFalse($entity->hasImageStyleMappings());
$entity->removeImageStyleMappings();
$entity->addImageStyleMapping('test_breakpoint', '1x', [
'image_mapping_type' => 'image_style',
'image_mapping' => 'large',
]);
$this->assertTrue($entity->hasImageStyleMappings());
$entity->removeImageStyleMappings();
$entity->addImageStyleMapping('test_breakpoint', '1x', [
'image_mapping_type' => 'sizes',
'image_mapping' => [
'sizes' => '(min-width:700px) 700px, 100vw',
'sizes_image_styles' => [
'large' => 'large',
],
],
]);
$this->assertTrue($entity->hasImageStyleMappings());
}
/**
* @covers ::addImageStyleMapping
* @covers ::getImageStyleMapping
*/
public function testGetImageStyleMapping(): void {
$entity = new ResponsiveImageStyle(['']);
$entity->addImageStyleMapping('test_breakpoint', '1x', [
'image_mapping_type' => 'image_style',
'image_mapping' => 'large',
]);
$expected = [
'breakpoint_id' => 'test_breakpoint',
'multiplier' => '1x',
'image_mapping_type' => 'image_style',
'image_mapping' => 'large',
];
$this->assertEquals($expected, $entity->getImageStyleMapping('test_breakpoint', '1x'));
$this->assertNull($entity->getImageStyleMapping('test_unknown_breakpoint', '1x'));
}
/**
* @covers ::addImageStyleMapping
* @covers ::getKeyedImageStyleMappings
*/
public function testGetKeyedImageStyleMappings(): void {
$entity = new ResponsiveImageStyle(['']);
$entity->addImageStyleMapping('test_breakpoint', '1x', [
'image_mapping_type' => 'image_style',
'image_mapping' => 'large',
]);
$entity->addImageStyleMapping('test_breakpoint', '2x', [
'image_mapping_type' => 'sizes',
'image_mapping' => [
'sizes' => '(min-width:700px) 700px, 100vw',
'sizes_image_styles' => [
'large' => 'large',
],
],
]);
$entity->addImageStyleMapping('test_breakpoint2', '1x', [
'image_mapping_type' => 'image_style',
'image_mapping' => 'thumbnail',
]);
$entity->addImageStyleMapping('test_breakpoint2', '2x', [
'image_mapping_type' => 'image_style',
'image_mapping' => '_original image_',
]);
$expected = [
'test_breakpoint' => [
'1x' => [
'breakpoint_id' => 'test_breakpoint',
'multiplier' => '1x',
'image_mapping_type' => 'image_style',
'image_mapping' => 'large',
],
'2x' => [
'breakpoint_id' => 'test_breakpoint',
'multiplier' => '2x',
'image_mapping_type' => 'sizes',
'image_mapping' => [
'sizes' => '(min-width:700px) 700px, 100vw',
'sizes_image_styles' => [
'large' => 'large',
],
],
],
],
'test_breakpoint2' => [
'1x' => [
'breakpoint_id' => 'test_breakpoint2',
'multiplier' => '1x',
'image_mapping_type' => 'image_style',
'image_mapping' => 'thumbnail',
],
'2x' => [
'breakpoint_id' => 'test_breakpoint2',
'multiplier' => '2x',
'image_mapping_type' => 'image_style',
'image_mapping' => '_original image_',
],
],
];
$this->assertEquals($expected, $entity->getKeyedImageStyleMappings());
// Add another mapping to ensure keyed mapping static cache is rebuilt.
$entity->addImageStyleMapping('test_breakpoint2', '2x', [
'image_mapping_type' => 'image_style',
'image_mapping' => 'medium',
]);
$expected['test_breakpoint2']['2x'] = [
'breakpoint_id' => 'test_breakpoint2',
'multiplier' => '2x',
'image_mapping_type' => 'image_style',
'image_mapping' => 'medium',
];
$this->assertEquals($expected, $entity->getKeyedImageStyleMappings());
// Overwrite a mapping to ensure keyed mapping static cache is rebuilt.
$entity->addImageStyleMapping('test_breakpoint2', '2x', [
'image_mapping_type' => 'image_style',
'image_mapping' => 'large',
]);
$expected['test_breakpoint2']['2x'] = [
'breakpoint_id' => 'test_breakpoint2',
'multiplier' => '2x',
'image_mapping_type' => 'image_style',
'image_mapping' => 'large',
];
$this->assertEquals($expected, $entity->getKeyedImageStyleMappings());
}
/**
* @covers ::addImageStyleMapping
* @covers ::getImageStyleMappings
*/
public function testGetImageStyleMappings(): void {
$entity = new ResponsiveImageStyle(['']);
$entity->addImageStyleMapping('test_breakpoint', '1x', [
'image_mapping_type' => 'image_style',
'image_mapping' => 'large',
]);
$entity->addImageStyleMapping('test_breakpoint', '2x', [
'image_mapping_type' => 'sizes',
'image_mapping' => [
'sizes' => '(min-width:700px) 700px, 100vw',
'sizes_image_styles' => [
'large' => 'large',
],
],
]);
$entity->addImageStyleMapping('test_breakpoint2', '1x', [
'image_mapping_type' => 'image_style',
'image_mapping' => 'thumbnail',
]);
$expected = [
[
'breakpoint_id' => 'test_breakpoint',
'multiplier' => '1x',
'image_mapping_type' => 'image_style',
'image_mapping' => 'large',
],
[
'breakpoint_id' => 'test_breakpoint',
'multiplier' => '2x',
'image_mapping_type' => 'sizes',
'image_mapping' => [
'sizes' => '(min-width:700px) 700px, 100vw',
'sizes_image_styles' => [
'large' => 'large',
],
],
],
[
'breakpoint_id' => 'test_breakpoint2',
'multiplier' => '1x',
'image_mapping_type' => 'image_style',
'image_mapping' => 'thumbnail',
],
];
$this->assertEquals($expected, $entity->getImageStyleMappings());
}
/**
* @covers ::addImageStyleMapping
* @covers ::removeImageStyleMappings
*/
public function testRemoveImageStyleMappings(): void {
$entity = new ResponsiveImageStyle(['']);
$entity->addImageStyleMapping('test_breakpoint', '1x', [
'image_mapping_type' => 'image_style',
'image_mapping' => 'large',
]);
$entity->addImageStyleMapping('test_breakpoint', '2x', [
'image_mapping_type' => 'sizes',
'image_mapping' => [
'sizes' => '(min-width:700px) 700px, 100vw',
'sizes_image_styles' => [
'large' => 'large',
],
],
]);
$entity->addImageStyleMapping('test_breakpoint2', '1x', [
'image_mapping_type' => 'image_style',
'image_mapping' => 'thumbnail',
]);
$this->assertTrue($entity->hasImageStyleMappings());
$entity->removeImageStyleMappings();
$this->assertEmpty($entity->getImageStyleMappings());
$this->assertEmpty($entity->getKeyedImageStyleMappings());
$this->assertFalse($entity->hasImageStyleMappings());
}
/**
* @covers ::setBreakpointGroup
* @covers ::getBreakpointGroup
*/
public function testSetBreakpointGroup(): void {
$entity = new ResponsiveImageStyle(['breakpoint_group' => 'test_group']);
$entity->addImageStyleMapping('test_breakpoint', '1x', [
'image_mapping_type' => 'image_style',
'image_mapping' => 'large',
]);
$entity->addImageStyleMapping('test_breakpoint', '2x', [
'image_mapping_type' => 'sizes',
'image_mapping' => [
'sizes' => '(min-width:700px) 700px, 100vw',
'sizes_image_styles' => [
'large' => 'large',
],
],
]);
$entity->addImageStyleMapping('test_breakpoint2', '1x', [
'image_mapping_type' => 'image_style',
'image_mapping' => 'thumbnail',
]);
// Ensure that setting to same group does not remove mappings.
$entity->setBreakpointGroup('test_group');
$this->assertTrue($entity->hasImageStyleMappings());
$this->assertEquals('test_group', $entity->getBreakpointGroup());
// Ensure that changing the group removes mappings.
$entity->setBreakpointGroup('test_group2');
$this->assertEquals('test_group2', $entity->getBreakpointGroup());
$this->assertFalse($entity->hasImageStyleMappings());
}
}