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')); + } + } + } + +}