diff --git a/composer.json b/composer.json
index b1ae7d8d2..7989fbb66 100644
--- a/composer.json
+++ b/composer.json
@@ -18,6 +18,7 @@
"behat/behat": "^3.10",
"behat/mink-extension": "^2.3.1",
"composer/installers": "~1.5",
+ "composer/xdebug-handler": "^2.0",
"cweagans/composer-patches": "^1.7",
"drupal/address": "~1.9",
"drupal/composite_reference": "^2.1",
@@ -52,7 +53,7 @@
"openeuropa/oe_corporate_countries": "~2.0",
"openeuropa/oe_media": "^1.15",
"openeuropa/oe_multilingual": "^1.10",
- "openeuropa/oe_paragraphs": "^1.11",
+ "openeuropa/oe_paragraphs": "dev-master",
"openeuropa/oe_search": "^1.9",
"openeuropa/oe_webtools": "^1.15",
"openeuropa/rdf_skos": "^1.0",
@@ -112,7 +113,8 @@
"Explicit minimum version requirement of drupal/ctools module due to D9.2 compatability.",
"Explicit minimum version requirement of behat library due to PHP compatibility.",
"Explicit requirement for egulias/email-validator due to https://www.drupal.org/project/drupal/issues/3061074#comment-14300579. It can be removed when Drupal core 9.2 support is droppped.",
- "Explicit minimum version requirement for symfony/dom-crawler due to its lower versions using the deprecated function libxml_disable_entity_loader() in PHP8."
+ "Explicit minimum version requirement for symfony/dom-crawler due to its lower versions using the deprecated function libxml_disable_entity_loader() in PHP8.",
+ "Requiring composer/xdebug-handler until PHPMD 2.12 is released"
]
},
"config": {
diff --git a/modules/oe_theme_helper/src/TwigExtension/TwigExtension.php b/modules/oe_theme_helper/src/TwigExtension/TwigExtension.php
index 21574fba6..5f8ea0f2a 100644
--- a/modules/oe_theme_helper/src/TwigExtension/TwigExtension.php
+++ b/modules/oe_theme_helper/src/TwigExtension/TwigExtension.php
@@ -275,210 +275,19 @@ public function toEclAttributes($attributes): array {
* Icon array for ECL components containing icon name, path and rotation.
*/
public function toEclIcon(array $context, $icon, string $size = ''): array {
- $path = $context['ecl_icon_path'];
- $social_path = $context['ecl_icon_social_media_path'];
-
- // ECL supported icons naming and rotation.
- $icons = [
- 'facebook' => [
- 'name' => 'facebook',
- 'social' => TRUE,
- ],
- 'instagram' => [
- 'name' => 'instagram',
- 'social' => TRUE,
- ],
- 'linkedin' => [
- 'name' => 'linkedin',
- 'social' => TRUE,
- ],
- 'pinterest' => [
- 'name' => 'pinterest',
- 'social' => TRUE,
- ],
- 'rss' => [
- 'name' => 'rss',
- ],
- 'skype' => [
- 'name' => 'skype',
- 'social' => TRUE,
- ],
- 'twitter' => [
- 'name' => 'twitter',
- 'social' => TRUE,
- ],
- 'youtube' => [
- 'name' => 'youtube',
- 'social' => TRUE,
- ],
- 'audio' => [
- 'name' => 'audio',
- ],
- 'book' => [
- 'name' => 'book',
- ],
- 'brochure' => [
- 'name' => 'brochure',
- ],
- 'budget' => [
- 'name' => 'budget',
- ],
- 'calendar' => [
- 'name' => 'calendar',
- ],
- 'copy' => [
- 'name' => 'copy',
- ],
- 'data' => [
- 'name' => 'data',
- ],
- 'digital' => [
- 'name' => 'digital',
- ],
- 'edit' => [
- 'name' => 'edit',
- ],
- 'energy' => [
- 'name' => 'energy',
- ],
- 'euro' => [
- 'name' => 'euro',
- ],
- 'faq' => [
- 'name' => 'faq',
- ],
- 'feedback' => [
- 'name' => 'feedback',
- ],
- 'file' => [
- 'name' => 'file',
- ],
- 'gear' => [
- 'name' => 'gear',
- ],
- 'generic-lang' => [
- 'name' => 'generic-lang',
- ],
- 'global' => [
- 'name' => 'global',
- ],
+ $path = $this->getIconPath($context, $icon);
+
+ // Icons that require transforming.
+ $transformed_icons = [
'googleplus' => [
'name' => 'digital',
],
- 'growth' => [
- 'name' => 'growth',
- ],
- 'hamburger' => [
- 'name' => 'hamburger',
- ],
- 'image' => [
- 'name' => 'image',
- ],
- 'infographic' => [
- 'name' => 'infographic',
- ],
- 'language' => [
- 'name' => 'language',
- ],
- 'livestreaming' => [
- 'name' => 'livestreaming',
- ],
- 'location' => [
- 'name' => 'location',
- ],
- 'log-in' => [
- 'name' => 'log-in',
- ],
- 'logged-in' => [
- 'name' => 'logged-in',
- ],
- 'multiple-files' => [
- 'name' => 'multiple-files',
- ],
- 'organigram' => [
- 'name' => 'organigram',
- ],
- 'package' => [
- 'name' => 'package',
- ],
- 'presentation' => [
- 'name' => 'presentation',
- ],
- 'print' => [
- 'name' => 'print',
- ],
- 'regulation' => [
- 'name' => 'regulation',
- ],
- 'search' => [
- 'name' => 'search',
- ],
- 'share' => [
- 'name' => 'share',
- ],
'slides' => [
'name' => 'presentation',
],
- 'spinner' => [
- 'name' => 'spinner',
- ],
- 'spreadsheet' => [
- 'name' => 'spreadsheet',
- ],
- 'video' => [
- 'name' => 'video',
- ],
- 'camera' => [
- 'name' => 'video',
- ],
- 'error' => [
- 'name' => 'error',
- ],
- 'information' => [
- 'name' => 'information',
- ],
'info' => [
'name' => 'information',
],
- 'success' => [
- 'name' => 'success',
- ],
- 'warning' => [
- 'name' => 'warning',
- ],
- 'check' => [
- 'name' => 'check',
- ],
- 'check-filled' => [
- 'name' => 'check-filled',
- ],
- 'close' => [
- 'name' => 'close',
- ],
- 'close-filled' => [
- 'name' => 'close-filled',
- ],
- 'corner-arrow' => [
- 'name' => 'corner-arrow',
- ],
- 'download' => [
- 'name' => 'download',
- ],
- 'external' => [
- 'name' => 'external',
- ],
- 'fullscreen' => [
- 'name' => 'fullscreen',
- ],
- 'minus' => [
- 'name' => 'minus',
- ],
- 'plus' => [
- 'name' => 'plus',
- ],
- 'solid-arrow' => [
- 'name' => 'solid-arrow',
- ],
'close-dark' => [
'name' => 'close-filled',
],
@@ -516,30 +325,112 @@ public function toEclIcon(array $context, $icon, string $size = ''): array {
],
];
- if (array_key_exists($icon, $icons)) {
- $icons[$icon]['path'] = $path;
- if (isset($icons[$icon]['social']) && $icons[$icon]['social']) {
- $icons[$icon]['path'] = $social_path;
- }
+ // Check whether the icon needs any transformation.
+ if (array_key_exists($icon, $transformed_icons)) {
+ $transformed_icons[$icon]['path'] = $path;
if ($size) {
- $icons[$icon]['size'] = $size;
+ $transformed_icons[$icon]['size'] = $size;
}
-
- return $icons[$icon];
+ return $transformed_icons[$icon];
}
+ // We define a default icon if one is not provided.
+ if (!$icon) {
+ $icon = 'digital';
+ }
+ $icon = [
+ 'name' => $icon,
+ 'path' => $path,
+ ];
if ($size) {
- return [
- 'name' => 'digital',
- 'path' => $path,
- 'size' => $size,
- ];
+ $icon['size'] = $size;
+
}
+ return $icon;
+ }
- return [
- 'name' => 'digital',
- 'path' => $path,
+ /**
+ * Returns the file path for an ECL icon.
+ *
+ * @param array $context
+ * The twig context.
+ * @param string $icon
+ * The icon to be converted.
+ *
+ * @return string
+ * ECL icon file path.
+ */
+ protected function getIconPath(array $context, string $icon): string {
+ // Flag icon names.
+ $flag_icons = [
+ 'austria',
+ 'belgium',
+ 'bulgaria',
+ 'croatia',
+ 'cyprus',
+ 'czech-republic',
+ 'denmark',
+ 'estonia',
+ 'EU',
+ 'finland',
+ 'france',
+ 'germany',
+ 'greece',
+ 'hungary',
+ 'ireland',
+ 'italy',
+ 'latvia',
+ 'lithuania',
+ 'luxembourg',
+ 'malta',
+ 'netherlands',
+ 'poland',
+ 'portugal',
+ 'romania',
+ 'slovakia',
+ 'slovenia',
+ 'spain',
+ 'sweden',
];
+ // Flag icons can have a -square string appended, so check if the icon name
+ // starts with a country name.
+ $found_icon = array_filter($flag_icons, function ($var) use ($icon) {
+ if (strpos($icon, $var) === 0) {
+ return TRUE;
+ };
+ return FALSE;
+ });
+ if ($found_icon) {
+ return $context['ecl_icon_flag_path'];
+ }
+
+ // Social media icon names.
+ $social_icons = [
+ 'blog',
+ 'facebook',
+ 'flickr',
+ 'foursquare',
+ 'instagram',
+ 'linkedin',
+ 'pinterest',
+ 'reddit',
+ 'skype',
+ 'spotify',
+ 'twitter',
+ 'youtube',
+ ];
+ // Social icons can have a -color or a -negative string appended,
+ // so check if the icon name starts with a social name.
+ $found_icon = array_filter($social_icons, function ($var) use ($icon) {
+ if (strpos($icon, $var) === 0) {
+ return TRUE;
+ };
+ return FALSE;
+ });
+ if ($found_icon) {
+ return $context['ecl_icon_social_media_path'];
+ }
+ return $context['ecl_icon_path'];
}
/**
diff --git a/modules/oe_theme_helper/tests/src/Unit/TwigExtensionTest.php b/modules/oe_theme_helper/tests/src/Unit/TwigExtensionTest.php
index 599aa2883..101324d0e 100644
--- a/modules/oe_theme_helper/tests/src/Unit/TwigExtensionTest.php
+++ b/modules/oe_theme_helper/tests/src/Unit/TwigExtensionTest.php
@@ -280,6 +280,7 @@ public function testToEclIcon(string $icon_name, array $expected_icon_array, str
$context = [
'ecl_icon_path' => '/path/to/theme/resources/icons/',
'ecl_icon_social_media_path' => '/path/to/theme/resources/social-media-icons/',
+ 'ecl_icon_flag_path' => '/path/to/theme/resources/flag-icons/',
];
// We join the resulting array from to_ecl_icon() function so that we have
// a visual representation of the array being returned by the function.
@@ -317,13 +318,38 @@ public function toEclIconProvider(): array {
'instagram',
[
'name' => 'instagram',
- // A TRUE value is rendered as 1 by twig.
- 'social' => 1,
'path' => '/path/to/theme/resources/social-media-icons/',
'size' => 'xs',
],
'xs',
],
+ [
+ 'instagram-color',
+ [
+ 'name' => 'instagram-color',
+ 'path' => '/path/to/theme/resources/social-media-icons/',
+ 'size' => 'xs',
+ ],
+ 'xs',
+ ],
+ [
+ 'spain',
+ [
+ 'name' => 'spain',
+ 'path' => '/path/to/theme/resources/flag-icons/',
+ 'size' => 'xs',
+ ],
+ 'xs',
+ ],
+ [
+ 'spain-square',
+ [
+ 'name' => 'spain-square',
+ 'path' => '/path/to/theme/resources/flag-icons/',
+ 'size' => 'xs',
+ ],
+ 'xs',
+ ],
[
'close-dark',
[
@@ -336,7 +362,7 @@ public function toEclIconProvider(): array {
[
'not-supported-icon',
[
- 'name' => 'digital',
+ 'name' => 'not-supported-icon',
'path' => '/path/to/theme/resources/icons/',
'size' => 'm',
],
@@ -345,7 +371,7 @@ public function toEclIconProvider(): array {
[
'no-size',
[
- 'name' => 'digital',
+ 'name' => 'no-size',
'path' => '/path/to/theme/resources/icons/',
],
NULL,
@@ -353,7 +379,7 @@ public function toEclIconProvider(): array {
[
'empty-size',
[
- 'name' => 'digital',
+ 'name' => 'empty-size',
'path' => '/path/to/theme/resources/icons/',
'size' => '',
],
diff --git a/oe_theme.theme b/oe_theme.theme
index 8b9535b88..18d84a574 100644
--- a/oe_theme.theme
+++ b/oe_theme.theme
@@ -10,12 +10,14 @@ declare(strict_types = 1);
use Drupal\block\BlockInterface;
use Drupal\Component\Utility\Html;
use Drupal\Core\Cache\CacheableMetadata;
+use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Url;
+use Drupal\Core\Session\AccountInterface;
use Drupal\media\MediaInterface;
use Drupal\media\Plugin\media\Source\Image;
use Drupal\media\Plugin\media\Source\OEmbed;
@@ -77,6 +79,7 @@ function oe_theme_preprocess(&$variables) {
$variables['ecl_logo_path'] = $variables['ecl_images_path'] . '/logo';
$variables['ecl_social_icon_path'] = base_path() . drupal_get_path('theme', 'oe_theme') . '/dist/ec/images/social-icons/sprites/icons-social.svg';
$variables['ecl_icon_social_media_path'] = $variables['ecl_images_path'] . '/icons-social-media/sprites/icons-social-media.svg';
+ $variables['ecl_icon_flag_path'] = $variables['ecl_images_path'] . '/icons-flag/sprites/icons-flag.svg';
$variables['current_language_id'] = \Drupal::languageManager()->getCurrentLanguage()->getId();
}
@@ -1739,6 +1742,162 @@ function oe_theme_preprocess_oe_theme_helper_site_navigation(&$variables) {
}
}
+/**
+ * Process common fields of illustration list paragraphs.
+ *
+ * @param array $variables
+ * Render array.
+ */
+function _oe_theme_preprocess_paragraph_oe_illustration_list(array &$variables): void {
+ $paragraph = $variables['paragraph'];
+ if (!$paragraph->get('field_oe_title')->isEmpty()) {
+ $variables['title'] = $paragraph->get('field_oe_title')->value;
+ }
+ $variables['zebra'] = (bool) $paragraph->get('field_oe_illustration_alternate')->value;
+
+ // Set the column value based on the selected variant.
+ $variant_value = $paragraph->get('oe_paragraphs_variant')->first()->value;
+ $variables['column'] = $variant_value === 'oe_illustration_vertical' ? 1 : $paragraph->get('field_oe_illustration_columns')->value;
+}
+
+/**
+ * Validate and prepare media entity to be processed in theme preprocesses.
+ *
+ * @param mixed $media
+ * MediaInterface entity will be processed only.
+ * @param \Drupal\Core\Session\AccountInterface $user
+ * Active user.
+ * @param \Drupal\Core\Cache\CacheableDependencyInterface $cacheability
+ * Object to collect cacheable dependencies.
+ * @param string $language_id
+ * Language of the Media to return.
+ *
+ * @return \Drupal\media\MediaInterface|null
+ * Media entity or null if media isn't accessible.
+ */
+function _oe_theme_prepare_media($media, AccountInterface $user, CacheableDependencyInterface $cacheability, string $language_id): ?MediaInterface {
+ if (!$media instanceof MediaInterface) {
+ // The media entity is not available.
+ return NULL;
+ }
+ $cacheability->addCacheableDependency($media);
+
+ // Run access checks on the media entity.
+ $access = $media->access('view', $user, TRUE);
+ $cacheability->addCacheableDependency($access);
+ if (!$access->isAllowed()) {
+ // Media isn't accessible.
+ return NULL;
+ }
+ // Retrieve the correct media translation.
+ return \Drupal::service('entity.repository')->getTranslationFromContext($media, $language_id);
+}
+
+/**
+ * Implements hook_preprocess_paragraph().
+ */
+function oe_theme_preprocess_paragraph__oe_illustration_list_flags(array &$variables): void {
+ _oe_theme_preprocess_paragraph_oe_illustration_list($variables);
+ $variables['items'] = [];
+
+ $paragraph = $variables['paragraph'];
+ $ratio = $paragraph->get('field_oe_illustration_ratio')->first()->value;
+ $builder = \Drupal::entityTypeManager()->getViewBuilder('paragraph');
+ /** @var \Drupal\paragraphs\Entity\Paragraph $sub_paragraph */
+ foreach ($paragraph->get('field_oe_paragraphs')->referencedEntities() as $sub_paragraph) {
+ // Get sub-paragraph translation.
+ $sub_paragraph = \Drupal::service('entity.repository')
+ ->getTranslationFromContext($sub_paragraph, $paragraph->language()->getId());
+ $icon = $sub_paragraph->get('field_oe_flag')->value;
+ $icon .= $ratio === 'square' ? '-square' : '';
+ $variables['items'][] = [
+ 'title' => $sub_paragraph->get('field_oe_title')->value,
+ 'description' => $sub_paragraph->get('field_oe_text_long')->isEmpty() ? '' : $builder->viewField($sub_paragraph->get('field_oe_text_long')),
+ 'icon' => $icon,
+ ];
+ }
+}
+
+/**
+ * Implements hook_preprocess_paragraph().
+ */
+function oe_theme_preprocess_paragraph__oe_illustration_list_icons(array &$variables): void {
+ _oe_theme_preprocess_paragraph_oe_illustration_list($variables);
+ $variables['items'] = [];
+
+ $paragraph = $variables['paragraph'];
+ $builder = \Drupal::entityTypeManager()->getViewBuilder('paragraph');
+ /** @var \Drupal\paragraphs\Entity\Paragraph $sub_paragraph */
+ foreach ($paragraph->get('field_oe_paragraphs')->referencedEntities() as $sub_paragraph) {
+ // Get sub-paragraph translation.
+ $sub_paragraph = \Drupal::service('entity.repository')
+ ->getTranslationFromContext($sub_paragraph, $paragraph->language()->getId());
+ $variables['items'][] = [
+ 'title' => $sub_paragraph->get('field_oe_title')->value,
+ 'description' => $sub_paragraph->get('field_oe_text_long')->isEmpty() ? '' : $builder->viewField($sub_paragraph->get('field_oe_text_long')),
+ 'icon' => $sub_paragraph->get('field_oe_icon')->value,
+ ];
+ }
+}
+
+/**
+ * Implements hook_preprocess_paragraph().
+ */
+function oe_theme_preprocess_paragraph__oe_illustration_list_images(array &$variables): void {
+ _oe_theme_preprocess_paragraph_oe_illustration_list($variables);
+
+ $paragraph = $variables['paragraph'];
+ $variables['items'] = [];
+
+ $entity_repository = \Drupal::service('entity.repository');
+ $cacheability = CacheableMetadata::createFromRenderArray($variables);
+ $builder = \Drupal::entityTypeManager()->getViewBuilder('paragraph');
+ /** @var \Drupal\paragraphs\Entity\Paragraph $sub_paragraph */
+ foreach ($paragraph->get('field_oe_paragraphs')->referencedEntities() as $sub_paragraph) {
+ // Get sub-paragraph translation.
+ $language_id = $paragraph->language()->getId();
+ $sub_paragraph = $entity_repository->getTranslationFromContext($sub_paragraph, $language_id);
+ // Get the image from attached media.
+ $image = [];
+ $media = _oe_theme_prepare_media($sub_paragraph->get('field_oe_media')->entity, $variables['user'], $cacheability, $language_id);
+ if ($media) {
+ $url = '';
+ $source = $media->getSource();
+ $field_name = $source->getConfiguration()['source_field'];
+ if ($source instanceof Image && ($file_entity = $media->get($field_name)->entity)) {
+ // Media Image.
+ $cacheability->addCacheableDependency($file_entity);
+ $url = $file_entity->createFileUrl();
+ }
+ elseif ($source instanceof MediaAvPortalSourceInterface) {
+ // Media AV Portal Photo.
+ $resource_ref = $media->get($field_name)->value;
+ $url = file_create_url('avportal://' . $resource_ref . '.jpg');
+ }
+
+ if ($url) {
+ // Generate image object for the template.
+ $values = [
+ 'src' => $url,
+ 'alt' => $source->getMetadata($media, 'thumbnail_alt_value') ?? $media->label(),
+ ];
+ $image = ImageValueObject::fromArray($values);
+ }
+ }
+ $variables['items'][] = [
+ 'title' => $sub_paragraph->get('field_oe_title')->value,
+ 'description' => $sub_paragraph->get('field_oe_text_long')->isEmpty() ? '' : $builder->viewField($sub_paragraph->get('field_oe_text_long')),
+ 'image' => $image,
+ ];
+ }
+
+ // Set "Square" or "Landscape" view for an image.
+ $ratio = $paragraph->get('field_oe_illustration_ratio')->first()->value;
+ $variables['square_image'] = $ratio === 'square' ? TRUE : FALSE;
+
+ $cacheability->applyTo($variables);
+}
+
/**
* Implements hook_preprocess_HOOK().
*/
diff --git a/package.json b/package.json
index d575b347e..2e37054f9 100644
--- a/package.json
+++ b/package.json
@@ -40,6 +40,7 @@
"@ecl/twig-component-inpage-navigation": "3.2.3",
"@ecl/twig-component-language-list": "3.2.3",
"@ecl/twig-component-link": "3.2.3",
+ "@ecl/twig-component-list-illustration": "3.2.3",
"@ecl/twig-component-label": "3.2.3",
"@ecl/twig-component-media-container": "3.2.3",
"@ecl/twig-component-menu": "3.2.3",
diff --git a/templates/paragraphs/paragraph--oe-illustration-list-flags.html.twig b/templates/paragraphs/paragraph--oe-illustration-list-flags.html.twig
new file mode 100644
index 000000000..e0a4e0f7c
--- /dev/null
+++ b/templates/paragraphs/paragraph--oe-illustration-list-flags.html.twig
@@ -0,0 +1,19 @@
+{#
+/**
+ * @file
+ * Theme override to display the 'Illustration list with flags' type paragraph.
+ *
+ * @see ./modules/contrib/paragraphs/templates/paragraph.html.twig
+ */
+#}
+{% if title is not empty %}
+
+ {{ title }}
+
+{% endif %}
+
+{{ pattern('list_with_illustration', {
+ 'column': column,
+ 'zebra': zebra,
+ 'items': items,
+}) }}
diff --git a/templates/paragraphs/paragraph--oe-illustration-list-icons.html.twig b/templates/paragraphs/paragraph--oe-illustration-list-icons.html.twig
new file mode 100644
index 000000000..ce73a23b5
--- /dev/null
+++ b/templates/paragraphs/paragraph--oe-illustration-list-icons.html.twig
@@ -0,0 +1,19 @@
+{#
+/**
+ * @file
+ * Theme override to display the 'Illustration list with icons' type paragraph.
+ *
+ * @see ./modules/contrib/paragraphs/templates/paragraph.html.twig
+ */
+#}
+{% if title is not empty %}
+
+ {{ title }}
+
+{% endif %}
+
+{{ pattern('list_with_illustration', {
+ 'column': column,
+ 'zebra': zebra,
+ 'items': items,
+}) }}
diff --git a/templates/paragraphs/paragraph--oe-illustration-list-images.html.twig b/templates/paragraphs/paragraph--oe-illustration-list-images.html.twig
new file mode 100644
index 000000000..a2ec7ef97
--- /dev/null
+++ b/templates/paragraphs/paragraph--oe-illustration-list-images.html.twig
@@ -0,0 +1,20 @@
+{#
+/**
+ * @file
+ * Theme override to display the 'Illustration list with images' type paragraph.
+ *
+ * @see ./modules/contrib/paragraphs/templates/paragraph.html.twig
+ */
+#}
+{% if title is not empty %}
+
+ {{ title }}
+
+{% endif %}
+
+{{ pattern('list_with_illustration', {
+ 'column': column,
+ 'zebra': zebra,
+ 'items': items,
+ 'square_image': square_image
+}) }}
diff --git a/templates/patterns/context_nav/js/contextual_navigation.js b/templates/patterns/context_nav/js/contextual_navigation.js
index 402fce636..85673849c 100644
--- a/templates/patterns/context_nav/js/contextual_navigation.js
+++ b/templates/patterns/context_nav/js/contextual_navigation.js
@@ -35,31 +35,37 @@
* @namespace
*/
Drupal.contextualNavigation = {
-
// Selector for list navigation element.
listSelector: '[data-ecl-contextual-navigation-list]',
// Selector for more item element.
moreItemSelector: '[data-ecl-contextual-navigation-more]',
-
+ // Selector for wrapper of more item element.
+ moreItemWrapperSelector: '.ecl-contextual-navigation__item--more',
initialize: function (element) {
var list = element.querySelector(this.listSelector);
if (list) {
- list
- .querySelector(this.moreItemSelector)
- .addEventListener('click', this.handleClickOnMore);
+ var moreItem = list.querySelector(this.moreItemSelector);
+ if (moreItem) {
+ moreItem.addEventListener('click', this.handleClickOnMore);
+ moreItem.list = list;
+ moreItem.wrapper = list.querySelector(this.moreItemWrapperSelector);
+ }
}
},
handleClickOnMore: function () {
- if (this.parentNode && this.parentNode.parentNode) {
- this.parentNode.parentNode.setAttribute('aria-expanded', 'true');
- this.parentNode.parentNode.removeChild(this.parentNode);
+ if (this.list) {
+ this.list.setAttribute('aria-expanded', 'true');
+ if (this.wrapper) {
+ this.wrapper.parentNode.removeChild(this.wrapper);
+ }
}
},
destroy: function (element) {
- element
- .querySelector(this.moreItemSelector)
- .removeEventListener('click', this.handleClickOnMore);
+ var moreItem = element.querySelector(this.moreItemSelector);
+ if (moreItem) {
+ moreItem.removeEventListener('click', this.handleClickOnMore);
+ }
}
}
})(Drupal);
diff --git a/templates/patterns/list_with_illustration/list_with_illustration.ui_patterns.yml b/templates/patterns/list_with_illustration/list_with_illustration.ui_patterns.yml
new file mode 100644
index 000000000..094d785f5
--- /dev/null
+++ b/templates/patterns/list_with_illustration/list_with_illustration.ui_patterns.yml
@@ -0,0 +1,38 @@
+list_with_illustration:
+ label: "List with illustration"
+ description: "A list of items with associated illustrations."
+ fields:
+ column:
+ type: "numeric"
+ label: "Column number"
+ description: "Number of columns. Maximum of 4 columns."
+ preview: 1
+ escape: false
+ zebra:
+ type: "boolean"
+ label: "Zebra"
+ description: "Use zebra background."
+ preview: true
+ square_image:
+ type: "boolean"
+ label: "Square image"
+ description: "Whether the rendered image should be rendered in square or landscape mode (only applies to image)."
+ preview: true
+ items:
+ type: "array"
+ label: "Items"
+ description: "Array of list items"
+ preview:
+ - title: "Business, Economy, Euro"
+ description: "EU economy, the euro, and practical information for EU businesses and entrepreneurs."
+ image:
+ src: "https://placeimg.com/1000/500/arch"
+ alt: "Alternative text for featured item image"
+ icon: "italy"
+ - title: "About the European Union"
+ description: "The EU and its institutions, how to visit and work at the EU."
+ image:
+ src: "https://placeimg.com/1000/500/arch"
+ alt: "Alternative text for featured item image"
+ icon: "budget"
+
diff --git a/templates/patterns/list_with_illustration/pattern-list-with-illustration.html.twig b/templates/patterns/list_with_illustration/pattern-list-with-illustration.html.twig
new file mode 100644
index 000000000..bce0ad3e1
--- /dev/null
+++ b/templates/patterns/list_with_illustration/pattern-list-with-illustration.html.twig
@@ -0,0 +1,29 @@
+{#
+/**
+ * @file
+ * List with illustration.
+ */
+#}
+{% if column > 1 %}
+ {# Do not show zebra style for horizontal view. #}
+ {% set zebra = false %}
+{% endif %}
+
+{% set _items = [] %}
+{% for item in items %}
+ {# Transform icon into ecl array. #}
+ {% if item.icon %}
+ {% set item = item|merge({'icon': to_ecl_icon(item.icon)}) %}
+ {% endif %}
+ {# Set square image if needed. #}
+ {% if item.image %}
+ {% set _image = item.image|merge({'squared': square_image}) %}
+ {% set item = item|merge({'image': _image}) %}
+ {% endif %}
+ {% set _items = _items|merge([item]) %}
+{% endfor %}
+{% include '@ecl-twig/list-illustration' with {
+ 'column': column,
+ 'zebra': zebra,
+ 'items': _items,
+} only %}
diff --git a/tests/modules/oe_theme_js_test/oe_theme_js_test.routing.yml b/tests/modules/oe_theme_js_test/oe_theme_js_test.routing.yml
index 7e23d318d..7214e6b6f 100644
--- a/tests/modules/oe_theme_js_test/oe_theme_js_test.routing.yml
+++ b/tests/modules/oe_theme_js_test/oe_theme_js_test.routing.yml
@@ -19,3 +19,10 @@ oe_theme_js_test.datepicker_test_form:
_title: 'DatePicker test form'
requirements:
_access: 'TRUE'
+oe_theme_js_test.contextual_navigation_ui_pattern:
+ path: '/oe_theme_js_test/ui_patterns/context_nav'
+ defaults:
+ _title: 'Contextual navigation page'
+ _controller: '\Drupal\oe_theme_js_test\Controller\UiPatterns::contextNav'
+ requirements:
+ _access: 'TRUE'
diff --git a/tests/modules/oe_theme_js_test/src/Controller/UiPatterns.php b/tests/modules/oe_theme_js_test/src/Controller/UiPatterns.php
new file mode 100644
index 000000000..1b594c611
--- /dev/null
+++ b/tests/modules/oe_theme_js_test/src/Controller/UiPatterns.php
@@ -0,0 +1,101 @@
+ 'pattern',
+ '#id' => 'context_nav',
+ '#fields' => [
+ 'label' => $this->t('Contextual navigation with more button'),
+ 'items' => [
+ [
+ 'href' => 'http://link-1.com',
+ 'label' => 'Item one',
+ ],
+ [
+ 'href' => 'http://link-2.com',
+ 'label' => 'Item two',
+ ],
+ [
+ 'href' => 'http://link-3.com',
+ 'label' => 'Item three',
+ ],
+ [
+ 'href' => 'http://link-4.com',
+ 'label' => 'Item four',
+ ],
+ [
+ 'href' => 'http://link-5.com',
+ 'label' => 'Item five',
+ ],
+ ],
+ 'limit' => 4,
+ 'more_label' => $this->t('More label'),
+ ],
+ ];
+
+ $build['context_nav_without_more_button'] = [
+ '#type' => 'pattern',
+ '#id' => 'context_nav',
+ '#fields' => [
+ 'label' => $this->t('Navigation title'),
+ 'items' => [
+ [
+ 'href' => 'http://link-1.com',
+ 'label' => 'Item one',
+ ],
+ [
+ 'href' => 'http://link-2.com',
+ 'label' => 'Item two',
+ ],
+ [
+ 'href' => 'http://link-3.com',
+ 'label' => 'Item three',
+ ],
+ ],
+ 'limit' => 4,
+ 'more_label' => $this->t('More label'),
+ ],
+ ];
+
+ $script = << 'script',
+ '#attributes' => [
+ 'type' => 'text/javascript',
+ ],
+ '#value' => Markup::create($script),
+ ], 'js_errors_collector',
+ ];
+
+ return $build;
+ }
+
+}
diff --git a/tests/src/FunctionalJavascript/JavascriptBehavioursTest.php b/tests/src/FunctionalJavascript/JavascriptBehavioursTest.php
index 96ef3cea1..6e1e043e0 100644
--- a/tests/src/FunctionalJavascript/JavascriptBehavioursTest.php
+++ b/tests/src/FunctionalJavascript/JavascriptBehavioursTest.php
@@ -191,4 +191,34 @@ public function testEclDatePicker(): void {
$this->assertSession()->pageTextContains('Date 1 is 10 May 2020');
}
+ /**
+ * Tests that contextual navigation pattern is rendered properly.
+ */
+ public function testContextNavPattern(): void {
+ $this->drupalGet('/oe_theme_js_test/ui_patterns/context_nav');
+ $script = <<getSession()->evaluateScript($script);
+ if ($errors) {
+ throw new \RuntimeException('Javascript error: ' . str_replace('[NLS]', PHP_EOL, $errors));
+ }
+
+ $this->assertCount(2, $this->getSession()->getPage()->findAll('css', '[data-ecl-contextual-navigation-list]'));
+ $this->assertCount(1, $this->getSession()->getPage()->findAll('css', '[data-ecl-contextual-navigation-more]'));
+
+ $this->getSession()->getPage()->pressButton('More label');
+ $this->assertCount(0, $this->getSession()->getPage()->findAll('css', '[data-ecl-contextual-navigation-more]'));
+ $this->assertCount(2, $this->getSession()->getPage()->findAll('css', '[data-ecl-contextual-navigation-list]'));
+ }
+
}
diff --git a/tests/src/Kernel/Paragraphs/IllustrationListsParagraphsTest.php b/tests/src/Kernel/Paragraphs/IllustrationListsParagraphsTest.php
new file mode 100644
index 000000000..4a8dba026
--- /dev/null
+++ b/tests/src/Kernel/Paragraphs/IllustrationListsParagraphsTest.php
@@ -0,0 +1,415 @@
+installEntitySchema('media');
+
+ module_load_include('install', 'media');
+ media_install();
+ $this->container->get('module_handler')->loadInclude('oe_paragraphs_media_field_storage', 'install');
+ oe_paragraphs_media_field_storage_install(FALSE);
+
+ $this->installConfig([
+ 'media',
+ 'options',
+ 'media_avportal',
+ 'oe_media',
+ 'oe_media_avportal',
+ 'oe_paragraphs_illustrations_lists',
+ ]);
+ }
+
+ /**
+ * Tests the rendering of the "Illustration list with flags" paragraph.
+ */
+ public function testIllustrationListFlagsRendering(): void {
+ // Create multiple paragraphs to be referenced in the illustration list.
+ $items = [];
+
+ $paragraph = Paragraph::create([
+ 'type' => 'oe_illustration_item_flag',
+ 'field_oe_title' => 'Term 1',
+ 'field_oe_text_long' => 'Description 1',
+ 'field_oe_flag' => 'austria',
+ ]);
+ $paragraph->save();
+ $items[] = $paragraph;
+
+ $paragraph = Paragraph::create([
+ 'type' => 'oe_illustration_item_flag',
+ 'field_oe_title' => 'Term 2',
+ 'field_oe_text_long' => '',
+ 'field_oe_flag' => 'belgium',
+ ]);
+ $paragraph->save();
+ $items[] = $paragraph;
+
+ $paragraph = Paragraph::create([
+ 'type' => 'oe_illustration_item_flag',
+ 'field_oe_title' => '',
+ 'field_oe_text_long' => 'Description 3',
+ 'field_oe_flag' => 'france',
+ ]);
+ $paragraph->save();
+ $items[] = $paragraph;
+
+ $paragraph = Paragraph::create([
+ 'type' => 'oe_illustration_item_flag',
+ 'field_oe_title' => '',
+ 'field_oe_text_long' => '',
+ 'field_oe_flag' => 'finland',
+ ]);
+ $paragraph->save();
+ $items[] = $paragraph;
+
+ $list_paragraph = Paragraph::create([
+ 'type' => 'oe_illustration_list_flags',
+ 'oe_paragraphs_variant' => 'default',
+ 'field_oe_title' => 'Illustration with flags test',
+ 'field_oe_paragraphs' => $items,
+ 'field_oe_illustration_columns' => 2,
+ 'field_oe_illustration_ratio' => 'landscape',
+ ]);
+ $html = $this->renderParagraph($list_paragraph);
+
+ // Assert paragraph header.
+ $crawler = new Crawler($html);
+ $heading = $crawler->filter('h2.ecl-u-type-heading-2');
+ $this->assertCount(1, $heading);
+ $this->assertEquals('Illustration with flags test', trim($heading->text()));
+ $icon = $crawler->filter('.ecl-list-illustration__icon use');
+ $this->assertStringNotContainsString('-square', $icon->attr('xlink:href'));
+
+ // Assert rendered items.
+ $expected_values = [
+ 'column' => 2,
+ 'zebra' => FALSE,
+ 'items' => [
+ [
+ 'title' => 'Term 1',
+ 'description' => 'Description 1',
+ 'icon' => 'austria',
+ ], [
+ 'title' => 'Term 2',
+ 'icon' => 'belgium',
+ ], [
+ 'description' => 'Description 3',
+ 'icon' => 'france',
+ ], [
+ 'icon' => 'finland',
+ ],
+ ],
+ ];
+ $assert = new ListWithIllustrationAssert();
+ $assert->assertPattern($expected_values, $html);
+
+ // Assert number of columns and ratio.
+ $list_paragraph->set('field_oe_illustration_columns', 4);
+ $list_paragraph->set('field_oe_illustration_ratio', 'square')->save();
+ $html = $this->renderParagraph($list_paragraph);
+ $expected_values = [
+ 'column' => 4,
+ 'zebra' => FALSE,
+ 'items' => [
+ [
+ 'title' => 'Term 1',
+ 'description' => 'Description 1',
+ 'icon' => 'austria-square',
+ ], [
+ 'title' => 'Term 2',
+ 'icon' => 'belgium-square',
+ ], [
+ 'description' => 'Description 3',
+ 'icon' => 'france-square',
+ ], [
+ 'icon' => 'finland-square',
+ ],
+ ],
+ ];
+ $assert->assertPattern($expected_values, $html);
+ $crawler = new Crawler($html);
+ $icon = $crawler->filter('.ecl-list-illustration__icon use');
+ $this->assertStringContainsString('-square', $icon->attr('xlink:href'));
+
+ // Assert vertical variant.
+ $list_paragraph->set('oe_paragraphs_variant', 'oe_illustration_vertical')->save();
+ $html = $this->renderParagraph($list_paragraph);
+ unset($expected_values['column']);
+ $assert->assertPattern($expected_values, $html);
+
+ // Assert vertical variant with zebra.
+ $list_paragraph->set('field_oe_illustration_alternate', TRUE)->save();
+ $html = $this->renderParagraph($list_paragraph);
+ $expected_values['zebra'] = TRUE;
+ $assert->assertPattern($expected_values, $html);
+ }
+
+ /**
+ * Tests the rendering of the "Illustration list with icons" paragraph.
+ */
+ public function testIllustrationListIconsRendering(): void {
+ $items = [];
+
+ $paragraph = Paragraph::create([
+ 'type' => 'oe_illustration_item_icon',
+ 'field_oe_title' => 'Term 1',
+ 'field_oe_text_long' => 'Description 1',
+ 'field_oe_icon' => 'data',
+ ]);
+ $paragraph->save();
+ $items[] = $paragraph;
+
+ $paragraph = Paragraph::create([
+ 'type' => 'oe_illustration_item_icon',
+ 'field_oe_title' => 'Term 2',
+ 'field_oe_icon' => 'facebook',
+ ]);
+ $paragraph->save();
+ $items[] = $paragraph;
+
+ $paragraph = Paragraph::create([
+ 'type' => 'oe_illustration_item_icon',
+ 'field_oe_text_long' => 'Description 3',
+ 'field_oe_icon' => 'global',
+ ]);
+ $paragraph->save();
+ $items[] = $paragraph;
+
+ $paragraph = Paragraph::create([
+ 'type' => 'oe_illustration_item_icon',
+ 'field_oe_icon' => 'package',
+ ]);
+ $paragraph->save();
+ $items[] = $paragraph;
+
+ $list_paragraph = Paragraph::create([
+ 'type' => 'oe_illustration_list_icons',
+ 'oe_paragraphs_variant' => 'default',
+ 'field_oe_title' => 'Illustration with icons test',
+ 'field_oe_paragraphs' => $items,
+ 'field_oe_illustration_columns' => 2,
+ ]);
+ $html = $this->renderParagraph($list_paragraph);
+
+ // Assert paragraph header.
+ $crawler = new Crawler($html);
+ $heading = $crawler->filter('h2.ecl-u-type-heading-2');
+ $this->assertCount(1, $heading);
+ $this->assertEquals('Illustration with icons test', trim($heading->text()));
+
+ // Assert rendered items.
+ $expected_values = [
+ 'column' => 2,
+ 'zebra' => FALSE,
+ 'items' => [
+ [
+ 'title' => 'Term 1',
+ 'description' => 'Description 1',
+ 'icon' => 'data',
+ ], [
+ 'title' => 'Term 2',
+ 'icon' => 'facebook',
+ ], [
+ 'description' => 'Description 3',
+ 'icon' => 'global',
+ ], [
+ 'icon' => 'package',
+ ],
+ ],
+ ];
+ $assert = new ListWithIllustrationAssert();
+ $assert->assertPattern($expected_values, $html);
+
+ // Assert number of columns.
+ $list_paragraph->set('field_oe_illustration_columns', 3)->save();
+ $html = $this->renderParagraph($list_paragraph);
+ $expected_values['column'] = 3;
+ $assert->assertPattern($expected_values, $html);
+
+ // Assert vertical variant.
+ $list_paragraph->set('oe_paragraphs_variant', 'oe_illustration_vertical')->save();
+ $html = $this->renderParagraph($list_paragraph);
+ unset($expected_values['column']);
+ $assert->assertPattern($expected_values, $html);
+
+ // Assert vertical variant with zebra.
+ $list_paragraph->set('field_oe_illustration_alternate', TRUE)->save();
+ $html = $this->renderParagraph($list_paragraph);
+ $expected_values['zebra'] = TRUE;
+ $assert->assertPattern($expected_values, $html);
+ }
+
+ /**
+ * Tests the rendering of the "Illustration list with icons" paragraph.
+ */
+ public function testIllustrationListImagesRendering(): void {
+ // Create media image.
+ $file = file_save_data(file_get_contents(drupal_get_path('theme', 'oe_theme') . '/tests/fixtures/example_1.jpeg'), 'public://example_1.jpeg');
+ $file->setPermanent();
+ $file->save();
+
+ $media_storage = $this->container->get('entity_type.manager')->getStorage('media');
+ $media_image = $media_storage->create([
+ 'bundle' => 'image',
+ 'name' => 'test image',
+ 'oe_media_image' => [
+ 'target_id' => $file->id(),
+ 'alt' => 'Alt',
+ ],
+ ]);
+ $media_image->save();
+
+ $media_av_portal_photo = $media_storage->create([
+ 'bundle' => 'av_portal_photo',
+ 'oe_media_avportal_photo' => 'P-038924/00-15',
+ 'uid' => 0,
+ 'status' => 1,
+ ]);
+
+ // Create Illustration list with images paragraph.
+ $items = [];
+ $paragraph = Paragraph::create([
+ 'type' => 'oe_illustration_item_image',
+ 'field_oe_title' => 'Term 1',
+ 'field_oe_text_long' => 'Description 1',
+ 'field_oe_media' => [$media_image],
+ ]);
+ $paragraph->save();
+ $items[] = $paragraph;
+
+ $paragraph = Paragraph::create([
+ 'type' => 'oe_illustration_item_image',
+ 'field_oe_title' => 'Term 2',
+ 'field_oe_media' => [$media_av_portal_photo],
+ ]);
+ $paragraph->save();
+ $items[] = $paragraph;
+
+ $paragraph = Paragraph::create([
+ 'type' => 'oe_illustration_item_image',
+ 'field_oe_text_long' => 'Description 3',
+ 'field_oe_media' => [$media_image],
+ ]);
+ $paragraph->save();
+ $items[] = $paragraph;
+
+ $paragraph = Paragraph::create([
+ 'type' => 'oe_illustration_item_image',
+ 'field_oe_media' => [$media_image],
+ ]);
+ $paragraph->save();
+ $items[] = $paragraph;
+
+ $list_paragraph = Paragraph::create([
+ 'type' => 'oe_illustration_list_images',
+ 'oe_paragraphs_variant' => 'default',
+ 'field_oe_title' => 'Illustration with images test',
+ 'field_oe_paragraphs' => $items,
+ 'field_oe_illustration_columns' => 2,
+ 'field_oe_illustration_ratio' => 'landscape',
+ ]);
+ $html = $this->renderParagraph($list_paragraph);
+
+ // Assert paragraph header.
+ $crawler = new Crawler($html);
+ $heading = $crawler->filter('h2.ecl-u-type-heading-2');
+ $this->assertCount(1, $heading);
+ $this->assertEquals('Illustration with images test', trim($heading->text()));
+
+ // Assert rendered items.
+ $expected_values = [
+ 'column' => 2,
+ 'zebra' => FALSE,
+ 'square_image' => FALSE,
+ 'items' => [
+ [
+ 'title' => 'Term 1',
+ 'description' => 'Description 1',
+ 'image' => [
+ 'src' => 'example_1.jpeg',
+ 'alt' => 'Alt',
+ ],
+ ], [
+ 'title' => 'Term 2',
+ 'image' => [
+ 'src' => file_create_url('avportal://P-038924/00-15.jpg'),
+ 'alt' => 'Euro with miniature figurines',
+ ],
+ ], [
+ 'description' => 'Description 3',
+ 'image' => [
+ 'src' => 'example_1.jpeg',
+ 'alt' => 'Alt',
+ ],
+ ], [
+ 'image' => [
+ 'src' => 'example_1.jpeg',
+ 'alt' => 'Alt',
+ ],
+ ],
+ ],
+ ];
+ $assert = new ListWithIllustrationAssert();
+ $assert->assertPattern($expected_values, $html);
+
+ // Assert number of columns and ratio.
+ $list_paragraph->set('field_oe_illustration_columns', 3);
+ $list_paragraph->set('field_oe_illustration_ratio', 'square')->save();
+ $html = $this->renderParagraph($list_paragraph);
+ $expected_values['column'] = 3;
+ $expected_values['square_image'] = TRUE;
+ $assert->assertPattern($expected_values, $html);
+
+ // Assert vertical variant.
+ $list_paragraph->set('oe_paragraphs_variant', 'oe_illustration_vertical')->save();
+ $html = $this->renderParagraph($list_paragraph);
+ unset($expected_values['column']);
+ $assert->assertPattern($expected_values, $html);
+
+ // Assert vertical variant with zebra and ratio.
+ $list_paragraph->set('field_oe_illustration_alternate', TRUE);
+ $list_paragraph->set('field_oe_illustration_ratio', 'landscape')->save();
+ $html = $this->renderParagraph($list_paragraph);
+ $expected_values['zebra'] = TRUE;
+ $expected_values['square_image'] = FALSE;
+ $assert->assertPattern($expected_values, $html);
+ }
+
+}
diff --git a/tests/src/Kernel/fixtures/rendering.yml b/tests/src/Kernel/fixtures/rendering.yml
index 0f958be66..5cedb70f7 100644
--- a/tests/src/Kernel/fixtures/rendering.yml
+++ b/tests/src/Kernel/fixtures/rendering.yml
@@ -3007,3 +3007,195 @@
'div.ecl-u-mr-xs.ecl-u-mt-s.ecl-tag.ecl-tag--removable': 'No link'
equals:
'div.active-search-filters__name span': 'Source'
+- array:
+ '#type': 'pattern'
+ '#id': 'list_with_illustration'
+ '#fields':
+ square_image: false
+ zebra: true
+ column: 1
+ items:
+ - title: "Business, Economy, Euro"
+ description: "EU economy, the euro, and practical information for EU businesses and entrepreneurs."
+ image:
+ src: "https://placeimg.com/1000/500/arch"
+ alt: "Alternative text for featured item image 1"
+ - title: "About the European Union"
+ description: "The EU and its institutions, how to visit and work at the EU."
+ image:
+ src: "https://placeimg.com/500/500/arch"
+ alt: "Alternative text for featured item image 2"
+ assertions:
+ count:
+ div.ecl-list-illustration: 1
+ div.ecl-list-illustration.ecl-list-illustration--zebra: 1
+ div.ecl-list-illustration__item: 2
+ div.ecl-list-illustration__image: 2
+ div.ecl-list-illustration__image.ecl-list-illustration__image--square: 0
+ div.ecl-list-illustration__image[aria-label="Alternative text for featured item image 1"]: 1
+ div.ecl-list-illustration__image[style="background-image:url('https://placeimg.com/1000/500/arch')"]: 1
+ div.ecl-list-illustration__image[aria-label="Alternative text for featured item image 2"]: 1
+ div.ecl-list-illustration__image[style="background-image:url('https://placeimg.com/500/500/arch')"]: 1
+ contains:
+ 'div.ecl-list-illustration__item:nth-child(1) div.ecl-list-illustration__title': 'Business, Economy, Euro'
+ 'div.ecl-list-illustration__item:nth-child(1) div.ecl-list-illustration__description': 'EU economy, the euro, and practical information for EU businesses and entrepreneurs.'
+ 'div.ecl-list-illustration__item:nth-child(2) div.ecl-list-illustration__title': 'About the European Union'
+ 'div.ecl-list-illustration__item:nth-child(2) div.ecl-list-illustration__description': 'The EU and its institutions, how to visit and work at the EU.'
+- array:
+ '#type': 'pattern'
+ '#id': 'list_with_illustration'
+ '#fields':
+ square_image: true
+ zebra: true
+ column: 1
+ items:
+ - title: "Business, Economy, Euro"
+ description: "EU economy, the euro, and practical information for EU businesses and entrepreneurs."
+ image:
+ src: "https://placeimg.com/1000/500/arch"
+ alt: "Alternative text for featured item image 1"
+ - title: "About the European Union"
+ description: "The EU and its institutions, how to visit and work at the EU."
+ image:
+ src: "https://placeimg.com/500/500/arch"
+ alt: "Alternative text for featured item image 2"
+ assertions:
+ count:
+ div.ecl-list-illustration: 1
+ div.ecl-list-illustration.ecl-list-illustration--zebra: 1
+ div.ecl-list-illustration__item: 2
+ div.ecl-list-illustration__image: 2
+ div.ecl-list-illustration__image.ecl-list-illustration__image--square: 2
+ div.ecl-list-illustration__image[aria-label="Alternative text for featured item image 1"]: 1
+ div.ecl-list-illustration__image[style="background-image:url('https://placeimg.com/1000/500/arch')"]: 1
+ div.ecl-list-illustration__image[aria-label="Alternative text for featured item image 2"]: 1
+ div.ecl-list-illustration__image[style="background-image:url('https://placeimg.com/500/500/arch')"]: 1
+ 'div.ecl-list-illustration__item svg.ecl-icon': 0
+ contains:
+ 'div.ecl-list-illustration__item:nth-child(1) div.ecl-list-illustration__title': 'Business, Economy, Euro'
+ 'div.ecl-list-illustration__item:nth-child(1) div.ecl-list-illustration__description': 'EU economy, the euro, and practical information for EU businesses and entrepreneurs.'
+ 'div.ecl-list-illustration__item:nth-child(2) div.ecl-list-illustration__title': 'About the European Union'
+ 'div.ecl-list-illustration__item:nth-child(2) div.ecl-list-illustration__description': 'The EU and its institutions, how to visit and work at the EU.'
+- array:
+ '#type': 'pattern'
+ '#id': 'list_with_illustration'
+ '#fields':
+ square_image: false
+ zebra: false
+ column: 1
+ items:
+ - title: "Business, Economy, Euro"
+ description: "EU economy, the euro, and practical information for EU businesses and entrepreneurs."
+ icon: "growth"
+ - title: "About the European Union"
+ description: "The EU and its institutions, how to visit and work at the EU."
+ icon: "budget"
+ assertions:
+ count:
+ div.ecl-list-illustration: 1
+ div.ecl-list-illustration.ecl-list-illustration--zebra: 0
+ div.ecl-list-illustration__item: 2
+ div.ecl-list-illustration__image: 0
+ contains:
+ 'div.ecl-list-illustration__item:nth-child(1) div.ecl-list-illustration__title': 'Business, Economy, Euro'
+ 'div.ecl-list-illustration__item:nth-child(1) div.ecl-list-illustration__description': 'EU economy, the euro, and practical information for EU businesses and entrepreneurs.'
+ 'div.ecl-list-illustration__item:nth-child(2) div.ecl-list-illustration__title': 'About the European Union'
+ 'div.ecl-list-illustration__item:nth-child(2) div.ecl-list-illustration__description': 'The EU and its institutions, how to visit and work at the EU.'
+ equals:
+ 'div.ecl-list-illustration__item:nth-child(1) svg.ecl-icon': ''
+ 'div.ecl-list-illustration__item:nth-child(2) svg.ecl-icon': ''
+- array:
+ '#type': 'pattern'
+ '#id': 'list_with_illustration'
+ '#fields':
+ square_image: false
+ zebra: true
+ column: 2
+ items:
+ - title: "Business, Economy, Euro"
+ description: "EU economy, the euro, and practical information for EU businesses and entrepreneurs."
+ image:
+ src: "https://placeimg.com/1000/500/arch"
+ alt: "Alternative text for featured item image 1"
+ - title: "About the European Union"
+ description: "The EU and its institutions, how to visit and work at the EU."
+ image:
+ src: "https://placeimg.com/500/500/arch"
+ alt: "Alternative text for featured item image 2"
+ assertions:
+ count:
+ div.ecl-list-illustration: 1
+ div.ecl-list-illustration.ecl-list-illustration--zebra: 0
+ div.ecl-list-illustration.ecl-list-illustration--col-2: 1
+ div.ecl-list-illustration__item: 2
+ div.ecl-list-illustration__image: 2
+ div.ecl-list-illustration__image.ecl-list-illustration__image--square: 0
+ div.ecl-list-illustration__image[aria-label="Alternative text for featured item image 1"]: 1
+ div.ecl-list-illustration__image[style="background-image:url('https://placeimg.com/1000/500/arch')"]: 1
+ div.ecl-list-illustration__image[aria-label="Alternative text for featured item image 2"]: 1
+ div.ecl-list-illustration__image[style="background-image:url('https://placeimg.com/500/500/arch')"]: 1
+ contains:
+ 'div.ecl-list-illustration__item:nth-child(1) div.ecl-list-illustration__title': 'Business, Economy, Euro'
+ 'div.ecl-list-illustration__item:nth-child(1) div.ecl-list-illustration__description': 'EU economy, the euro, and practical information for EU businesses and entrepreneurs.'
+ 'div.ecl-list-illustration__item:nth-child(2) div.ecl-list-illustration__title': 'About the European Union'
+ 'div.ecl-list-illustration__item:nth-child(2) div.ecl-list-illustration__description': 'The EU and its institutions, how to visit and work at the EU.'
+- array:
+ '#type': 'pattern'
+ '#id': 'list_with_illustration'
+ '#fields':
+ square_image: true
+ column: 3
+ items:
+ - title: "Business, Economy, Euro"
+ description: "EU economy, the euro, and practical information for EU businesses and entrepreneurs."
+ image:
+ src: "https://placeimg.com/1000/500/arch"
+ alt: "Alternative text for featured item image 1"
+ - title: "About the European Union"
+ description: "The EU and its institutions, how to visit and work at the EU."
+ image:
+ src: "https://placeimg.com/500/500/arch"
+ alt: "Alternative text for featured item image 2"
+ assertions:
+ count:
+ div.ecl-list-illustration: 1
+ div.ecl-list-illustration.ecl-list-illustration--col-3: 1
+ div.ecl-list-illustration__item: 2
+ div.ecl-list-illustration__image: 2
+ div.ecl-list-illustration__image.ecl-list-illustration__image--square: 2
+ div.ecl-list-illustration__image[aria-label="Alternative text for featured item image 1"]: 1
+ div.ecl-list-illustration__image[style="background-image:url('https://placeimg.com/1000/500/arch')"]: 1
+ div.ecl-list-illustration__image[aria-label="Alternative text for featured item image 2"]: 1
+ div.ecl-list-illustration__image[style="background-image:url('https://placeimg.com/500/500/arch')"]: 1
+ contains:
+ 'div.ecl-list-illustration__item:nth-child(1) div.ecl-list-illustration__title': 'Business, Economy, Euro'
+ 'div.ecl-list-illustration__item:nth-child(1) div.ecl-list-illustration__description': 'EU economy, the euro, and practical information for EU businesses and entrepreneurs.'
+ 'div.ecl-list-illustration__item:nth-child(2) div.ecl-list-illustration__title': 'About the European Union'
+ 'div.ecl-list-illustration__item:nth-child(2) div.ecl-list-illustration__description': 'The EU and its institutions, how to visit and work at the EU.'
+- array:
+ '#type': 'pattern'
+ '#id': 'list_with_illustration'
+ '#fields':
+ square_image: false
+ column: 2
+ items:
+ - title: "Business, Economy, Euro"
+ description: "EU economy, the euro, and practical information for EU businesses and entrepreneurs."
+ icon: "growth"
+ - title: "About the European Union"
+ description: "The EU and its institutions, how to visit and work at the EU."
+ icon: "budget"
+ assertions:
+ count:
+ div.ecl-list-illustration: 1
+ div.ecl-list-illustration.ecl-list-illustration--col-2: 1
+ div.ecl-list-illustration__item: 2
+ div.ecl-list-illustration__image: 0
+ contains:
+ 'div.ecl-list-illustration__item:nth-child(1) div.ecl-list-illustration__title': 'Business, Economy, Euro'
+ 'div.ecl-list-illustration__item:nth-child(1) div.ecl-list-illustration__description': 'EU economy, the euro, and practical information for EU businesses and entrepreneurs.'
+ 'div.ecl-list-illustration__item:nth-child(2) div.ecl-list-illustration__title': 'About the European Union'
+ 'div.ecl-list-illustration__item:nth-child(2) div.ecl-list-illustration__description': 'The EU and its institutions, how to visit and work at the EU.'
+ equals:
+ 'div.ecl-list-illustration__item:nth-child(1) svg.ecl-icon': ''
+ 'div.ecl-list-illustration__item:nth-child(2) svg.ecl-icon': ''
diff --git a/tests/src/PatternAssertions/ListWithIllustrationAssert.php b/tests/src/PatternAssertions/ListWithIllustrationAssert.php
new file mode 100644
index 000000000..d79709ce3
--- /dev/null
+++ b/tests/src/PatternAssertions/ListWithIllustrationAssert.php
@@ -0,0 +1,129 @@
+ [
+ [$this, 'assertColumns'],
+ ],
+ 'zebra' => [
+ [$this, 'assertZebra'],
+ ],
+ 'square_image' => [
+ [$this, 'assertSquareImage'],
+ ],
+ 'items' => [
+ [$this, 'assertItems'],
+ ],
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function assertBaseElements(string $html, string $variant): void {
+ $crawler = new Crawler($html);
+ self::assertElementExists('.ecl-list-illustration', $crawler);
+ }
+
+ /**
+ * Asserts the columns of the list.
+ *
+ * @param int $column
+ * The expected number of columns.
+ * @param \Symfony\Component\DomCrawler\Crawler $crawler
+ * The DomCrawler where to check the element.
+ */
+ protected function assertColumns(int $column, Crawler $crawler): void {
+ $column_selector = '.ecl-list-illustration--col-' . $column;
+ self::assertElementExists($column_selector, $crawler);
+ }
+
+ /**
+ * Asserts if the list uses zebra pattern or not.
+ *
+ * @param bool $zebra
+ * Whether the zebra pattern is enabled or not.
+ * @param \Symfony\Component\DomCrawler\Crawler $crawler
+ * The DomCrawler where to check the element.
+ */
+ protected function assertZebra(bool $zebra, Crawler $crawler): void {
+ if ($zebra) {
+ self::assertElementExists('.ecl-list-illustration--zebra', $crawler);
+ }
+ else {
+ self::assertElementNotExists('.ecl-list-illustration--zebra', $crawler);
+ }
+ }
+
+ /**
+ * Asserts if the list uses square images or not.
+ *
+ * @param bool $square_image
+ * Whether the "square_image" option is enabled or not.
+ * @param \Symfony\Component\DomCrawler\Crawler $crawler
+ * The DomCrawler where to check the element.
+ */
+ protected function assertSquareImage(bool $square_image, Crawler $crawler): void {
+ if ($square_image) {
+ // Few square images can exist.
+ $element = $crawler->filter('.ecl-list-illustration__image--square');
+ self::assertTrue((bool) $element->count());
+ }
+ else {
+ self::assertElementNotExists('.ecl-list-illustration__image--square', $crawler);
+ }
+ }
+
+ /**
+ * Asserts the item with an illustration from the list.
+ *
+ * @param array $expected_items
+ * The expected list items.
+ * @param \Symfony\Component\DomCrawler\Crawler $crawler
+ * The DomCrawler where to check the element.
+ */
+ protected function assertItems(array $expected_items, Crawler $crawler): void {
+ $item_elements = $crawler->filter('.ecl-list-illustration__item');
+ self::assertCount(count($expected_items), $item_elements, 'The expected list items do not match the found list items.');
+ foreach ($expected_items as $index => $expected_item) {
+ $item_element = $item_elements->eq($index);
+ if (isset($expected_item['title'])) {
+ self::assertElementText($expected_item['title'], '.ecl-list-illustration__title', $item_element);
+ }
+ else {
+ self::assertElementNotExists('.ecl-list-illustration__title', $item_element);
+ }
+ if (isset($expected_item['description'])) {
+ self::assertElementText($expected_item['description'], '.ecl-list-illustration__description', $item_element);
+ }
+ else {
+ self::assertElementNotExists('.ecl-list-illustration__description', $item_element);
+ }
+ if (isset($expected_item['image'])) {
+ self::assertElementExists('.ecl-list-illustration__image', $item_element);
+ $image_element = $item_element->filter('.ecl-list-illustration__image');
+ self::assertEquals($expected_item['image']['alt'], $image_element->attr('aria-label'));
+ self::assertStringContainsString($expected_item['image']['src'], $image_element->attr('style'));
+ }
+ if (isset($expected_item['icon'])) {
+ $icon_element = $item_element->filter('svg.ecl-icon use');
+ $this::assertStringContainsString('#' . $expected_item['icon'], $icon_element->attr('xlink:href'));
+ }
+ }
+ }
+
+}