diff --git a/.gitignore b/.gitignore index f5e9d58a8791a1f7513057ef460f91418e7f043c..6d50f6680193bc6647d82f15fa85cb76522bf1da 100644 --- a/.gitignore +++ b/.gitignore @@ -43,9 +43,6 @@ /.vscode/ /.devcontainer/ -# Ignore the cookie consent killswitch. -/disable-cookie-consent - # Ignore temporary and auto-generated files. /tmp/ /web/VERSION.txt diff --git a/behat.yml.dist b/behat.yml.dist index 1c8df9bf3f51e6614240de22dfdea196dfdcb78a..202b73590393d7f0e928d41ea0e3e75de8f77352 100644 --- a/behat.yml.dist +++ b/behat.yml.dist @@ -3,6 +3,7 @@ - '%paths.base%/tests/features' .contexts: &contexts - Drupal\joinup\Context\BootstrapDrupalContext + - Drupal\DrupalExtension\Context\ConfigContext - Drupal\DrupalExtension\Context\DrushContext - Drupal\DrupalExtension\Context\MarkupContext - Drupal\DrupalExtension\Context\MessageContext @@ -67,6 +68,7 @@ - Drupal\joinup\Context\JoinupSeoContext - Drupal\joinup\Context\JoinupSubscriptionContext - Drupal\joinup\Context\JoinupUserContext + - Drupal\joinup\Context\JsErrorsContext - Drupal\joinup\Context\LinkContext - Drupal\joinup\Context\LogRecordsContext - Drupal\joinup\Context\MediaContext diff --git a/composer.json b/composer.json index 8b1b22677f4d1cb157ebae5c33650823fed90100..9b94c8589c960c2405b2805789907bc15d0c9c16 100644 --- a/composer.json +++ b/composer.json @@ -338,7 +338,8 @@ "The submit button is not following any condition while in popup/dialogs @see https://drupal.org/i/3008172": "resources/patch/php/drupal/core/3008172-43.patch", "Added deprecation suppression": "resources/patch/php/drupal/core/3467293.diff", "Fix handling of unknown file extensions in FileMediaFormatterBase @see https://www.drupal.org/i/3466462": "resources/patch/php/drupal/core/3466462.diff", - "Allow ChangedItem to skip updating the entity's \"changed\" timestamp when synchronizing @see https://drupal.org/i/2329253": "resources/patch/php/drupal/core/2329253-10.x.patch" + "Allow ChangedItem to skip updating the entity's \"changed\" timestamp when synchronizing @see https://drupal.org/i/2329253": "resources/patch/php/drupal/core/2329253-10.x.patch", + "JS errors from Drupal.dialog.resetSize when rapidly closing dialogs @see https://www.drupal.org/i/3472624": "resources/patch/php/drupal/core/3472624.diff" }, "drupal/default_content": { "Allow manual imports @see https://www.drupal.org/i/2640734": "resources/patch/php/drupal/default_content/2640734.diff" @@ -387,7 +388,8 @@ }, "drupal/honeypot": { "Behat integration. @see https://www.drupal.org/project/honeypot/issues/3059040": "resources/patch/php/drupal/honeypot/3059040.patch", - "Provide ways to clean up old honeypot_time_restriction values @see https://www.drupal.org/project/honeypot/issues/2997609": "resources/patch/php/drupal/honeypot/2997609.patch" + "Provide ways to clean up old honeypot_time_restriction values @see https://www.drupal.org/project/honeypot/issues/2997609": "resources/patch/php/drupal/honeypot/2997609.patch", + "Remove caching in HoneypotService::getProtectedForms preventing proper invalidation @see https://www.drupal.org/i/3481399": "resources/patch/php/drupal/honeypot/3481399.patch" }, "drupal/imagecache_external": { "Support SVG @see https://www.drupal.org/i/3390948": "resources/patch/php/drupal/imagecache_external/3390948.diff", diff --git a/composer.lock b/composer.lock index bd86abbded8d744dd98c5df00add373081a930dc..3b386029cca7a43a87190abc7ff0875398617dbc 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "cf8792d8c3cb566dd5b99db3e9839e0a", + "content-hash": "c60614e39d086e7f4dc1e3a2cab0d2f4", "packages": [ { "name": "asm89/stack-cors", diff --git a/resources/patch/php/drupal/core/3472624.diff b/resources/patch/php/drupal/core/3472624.diff new file mode 100644 index 0000000000000000000000000000000000000000..2af9300c49a69c644f831416b6000e106bde455e --- /dev/null +++ b/resources/patch/php/drupal/core/3472624.diff @@ -0,0 +1,41 @@ +diff --git a/core/misc/dialog/dialog.position.js b/core/misc/dialog/dialog.position.js +index 98662211595ede9a4d1a238f90c0cae8cfe006e3..a15f726d1c5afca5586580dd27e82f981fc6472b 100644 +--- a/core/misc/dialog/dialog.position.js ++++ b/core/misc/dialog/dialog.position.js +@@ -63,6 +63,14 @@ + * @fires event:dialogContentResize + */ + function resetSize(event) { ++ // Ensure the UI dialog instance exists/is valid. ++ // If the dialog is closed very rapidly, the jQuery UI instance may have ++ // been destroyed, but debounce might still call this function within the ++ // setTimeout interval. ++ if (!event.data.$element.data('ui-dialog')) { ++ return; ++ } ++ + const positionOptions = [ + 'width', + 'height', +diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/DialogTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/DialogTest.php +index bdd324020866d453050e89517767df29de6e6bb2..bbbc60000b23570cead191360943aa481077fafd 100644 +--- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/DialogTest.php ++++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/DialogTest.php +@@ -69,6 +69,17 @@ public function testDialog(): void { + $this->assertNotNull($close_button); + $close_button->press(); + ++ // Test opening and immediately closing modal a few times. ++ // Ensure no JS errors are thrown. ++ for ($i = 0; $i < 10; $i++) { ++ $this->getSession()->getPage()->clickLink('Link 1 (modal)'); ++ $this->assertSession()->waitForElementVisible('css', 'div.ui-dialog'); ++ // Use JS directly which is much faster than Element::find. ++ // We want to close it as fast as possible, within 20ms, corresponding to ++ // the debounce on Drupal.dialog.resetSize. ++ $this->getSession()->executeScript('document.querySelector(".ui-dialog button[title=\"Close\"]").click();'); ++ } ++ + // Tests a modal with a dialog-option. + // Link 2 is similar to Link 1, except it submits additional width + // information which must be echoed in the resulting DOM update. diff --git a/resources/patch/php/drupal/honeypot/3481399.patch b/resources/patch/php/drupal/honeypot/3481399.patch new file mode 100644 index 0000000000000000000000000000000000000000..9dfe2153d6abe315b36cd57a43fbd34d1bf38207 --- /dev/null +++ b/resources/patch/php/drupal/honeypot/3481399.patch @@ -0,0 +1,228 @@ +diff --git a/honeypot.services.yml b/honeypot.services.yml +index 331a8c2..bd80d0d 100644 +--- a/honeypot.services.yml ++++ b/honeypot.services.yml +@@ -1,4 +1,4 @@ + services: + honeypot: + class: Drupal\honeypot\HoneypotService +- arguments: ['@current_user', '@module_handler', '@config.factory', '@keyvalue.expirable', '@page_cache_kill_switch', '@database', '@logger.factory', '@datetime.time', '@string_translation', '@cache.default', '@request_stack'] ++ arguments: ['@current_user', '@module_handler', '@config.factory', '@keyvalue.expirable', '@page_cache_kill_switch', '@database', '@logger.factory', '@datetime.time', '@string_translation', '@request_stack'] +diff --git a/src/Form/HoneypotSettingsForm.php b/src/Form/HoneypotSettingsForm.php +index cbf64b7..2116d72 100644 +--- a/src/Form/HoneypotSettingsForm.php ++++ b/src/Form/HoneypotSettingsForm.php +@@ -3,7 +3,6 @@ + namespace Drupal\honeypot\Form; + + use Drupal\Component\Utility\Html; +-use Drupal\Core\Cache\CacheBackendInterface; + use Drupal\Core\Config\ConfigFactoryInterface; + use Drupal\Core\Config\TypedConfigManagerInterface; + use Drupal\Core\Entity\EntityTypeBundleInfoInterface; +@@ -40,13 +39,6 @@ class HoneypotSettingsForm extends ConfigFormBase { + */ + protected $entityTypeBundleInfo; + +- /** +- * A cache backend interface. +- * +- * @var \Drupal\Core\Cache\CacheBackendInterface +- */ +- protected $cache; +- + /** + * Constructs a settings controller. + * +@@ -60,15 +52,12 @@ class HoneypotSettingsForm extends ConfigFormBase { + * The entity type manager. + * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info + * The entity type bundle info service. +- * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend +- * The cache backend interface. + */ +- public function __construct(ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typedConfigManager, ModuleHandlerInterface $module_handler, EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info, CacheBackendInterface $cache_backend) { ++ public function __construct(ConfigFactoryInterface $config_factory, TypedConfigManagerInterface $typedConfigManager, ModuleHandlerInterface $module_handler, EntityTypeManagerInterface $entity_type_manager, EntityTypeBundleInfoInterface $entity_type_bundle_info) { + parent::__construct($config_factory, $typedConfigManager); + $this->moduleHandler = $module_handler; + $this->entityTypeManager = $entity_type_manager; + $this->entityTypeBundleInfo = $entity_type_bundle_info; +- $this->cache = $cache_backend; + } + + /** +@@ -80,8 +69,7 @@ class HoneypotSettingsForm extends ConfigFormBase { + $container->get('config.typed'), + $container->get('module_handler'), + $container->get('entity_type.manager'), +- $container->get('entity_type.bundle.info'), +- $container->get('cache.default') ++ $container->get('entity_type.bundle.info') + ); + } + +@@ -295,12 +283,7 @@ class HoneypotSettingsForm extends ConfigFormBase { + } + + // Save the honeypot forms from $form_state into a 'form_settings' array. +- $config->set('form_settings', $form_state->getValue('form_settings')); +- +- $config->save(); +- +- // Clear the honeypot protected forms cache. +- $this->cache->delete('honeypot_protected_forms'); ++ $config->set('form_settings', $form_state->getValue('form_settings'))->save(); + + parent::submitForm($form, $form_state); + } +diff --git a/src/HoneypotService.php b/src/HoneypotService.php +index 22ea589..e767c32 100644 +--- a/src/HoneypotService.php ++++ b/src/HoneypotService.php +@@ -4,7 +4,6 @@ namespace Drupal\honeypot; + + use Drupal\Component\Datetime\TimeInterface; + use Drupal\Component\Utility\Crypt; +-use Drupal\Core\Cache\CacheBackendInterface; + use Drupal\Core\Config\ConfigFactory; + use Drupal\Core\Database\Connection; + use Drupal\Core\DependencyInjection\DependencySerializationTrait; +@@ -81,13 +80,6 @@ class HoneypotService implements HoneypotServiceInterface { + */ + protected $timeService; + +- /** +- * A cache backend interface. +- * +- * @var \Drupal\Core\Cache\CacheBackendInterface +- */ +- protected $cacheBackend; +- + /** + * The request stack service. + * +@@ -116,12 +108,10 @@ class HoneypotService implements HoneypotServiceInterface { + * The datetime.time service. + * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation + * The string translation service. +- * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend +- * The cache backend interface. + * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack + * The request stack service. + */ +- public function __construct(AccountProxyInterface $account, ModuleHandlerInterface $module_handler, ConfigFactory $config_factory, KeyValueExpirableFactory $key_value, KillSwitch $kill_switch, Connection $connection, LoggerChannelFactoryInterface $logger_factory, TimeInterface $time_service, TranslationInterface $string_translation, CacheBackendInterface $cache_backend, RequestStack $request_stack) { ++ public function __construct(AccountProxyInterface $account, ModuleHandlerInterface $module_handler, ConfigFactory $config_factory, KeyValueExpirableFactory $key_value, KillSwitch $kill_switch, Connection $connection, LoggerChannelFactoryInterface $logger_factory, TimeInterface $time_service, TranslationInterface $string_translation, RequestStack $request_stack) { + $this->account = $account; + $this->moduleHandler = $module_handler; + $this->config = $config_factory->get('honeypot.settings'); +@@ -131,7 +121,6 @@ class HoneypotService implements HoneypotServiceInterface { + $this->loggerFactory = $logger_factory; + $this->timeService = $time_service; + $this->stringTranslation = $string_translation; +- $this->cacheBackend = $cache_backend; + $this->requestStack = $request_stack; + } + +@@ -139,31 +128,7 @@ class HoneypotService implements HoneypotServiceInterface { + * {@inheritdoc} + */ + public function getProtectedForms(): array { +- $forms = &drupal_static(__METHOD__); +- +- // If the data isn't already in memory, get from cache or look it up fresh. +- if (!isset($forms)) { +- if ($cache = $this->cacheBackend->get('honeypot_protected_forms')) { +- $forms = $cache->data; +- } +- else { +- $forms = []; +- $form_settings = $this->config->get('form_settings'); +- if (!empty($form_settings)) { +- // Add each form that's enabled to the $forms array. +- foreach ($form_settings as $form_id => $enabled) { +- if ($enabled) { +- $forms[] = $form_id; +- } +- } +- } +- +- // Save the cached data. +- $this->cacheBackend->set('honeypot_protected_forms', $forms); +- } +- } +- +- return $forms; ++ return array_keys(array_filter($this->config->get('form_settings') ?? [])); + } + + /** +diff --git a/tests/src/Kernel/HoneypotTest.php b/tests/src/Kernel/HoneypotTest.php +new file mode 100644 +index 0000000..ae3610f +--- /dev/null ++++ b/tests/src/Kernel/HoneypotTest.php +@@ -0,0 +1,64 @@ ++<?php ++ ++declare(strict_types=1); ++ ++namespace Drupal\Tests\honeypot\Kernel; ++ ++use Drupal\KernelTests\KernelTestBase; ++ ++/** ++ * Tests honeypot functionality. ++ * ++ * @group honeypot ++ */ ++class HoneypotTest extends KernelTestBase { ++ ++ /** ++ * {@inheritdoc} ++ */ ++ protected static $modules = ['honeypot']; ++ ++ /** ++ * The config factory service. ++ * ++ * @var \Drupal\Core\Config\ConfigFactoryInterface ++ */ ++ protected $configFactory; ++ ++ /** ++ * The Honeypot service. ++ * ++ * @var \Drupal\honeypot\HoneypotServiceInterface ++ */ ++ protected $honeypot; ++ ++ /** ++ * {@inheritdoc} ++ */ ++ protected function setUp(): void { ++ parent::setUp(); ++ ++ $this->installSchema('honeypot', []); ++ $this->installConfig(['honeypot']); ++ $this->configFactory = $this->container->get('config.factory'); ++ $this->honeypot = $this->container->get('honeypot'); ++ } ++ ++ /** ++ * Tests the Honeypot protected forms method. ++ * ++ * @covers \Drupal\honeypot\HoneypotService::getProtectedForms ++ */ ++ public function testGetProtectedForms(): void { ++ $config = $this->configFactory->getEditable('honeypot.settings'); ++ ++ // Initial state: we have a protected form. ++ $config->set('form_settings', ['user_register_form' => TRUE])->save(); ++ $this->assertEquals(['user_register_form'], $this->honeypot->getProtectedForms()); ++ ++ // Empty form_settings, expect no protected forms. ++ $config->set('form_settings', [])->save(); ++ $this->assertEquals([], $this->honeypot->getProtectedForms()); ++ } ++ ++} diff --git a/resources/runner/drupal.yml b/resources/runner/drupal.yml index 2b0fc17ae356ed2a53ab142270962acbc31f1d68..74bb7f38350cc59a235789fe088f6282ad92338f 100644 --- a/resources/runner/drupal.yml +++ b/resources/runner/drupal.yml @@ -253,12 +253,11 @@ drupal: // Location of the site configuration files. $settings['config_sync_directory'] = '../config/sync'; Testing configuration alters: | + // Enable test_mode. + $config['joinup_test.settings']['test_mode'] = TRUE; + // The video from the home page interacts with Selenium tests. $config['page_manager.page_variant.homepage-layout_builder-0']['variant_settings']['sections'][4]['components']['6ab0ceea-4541-4153-8b64-227e02369d30']['configuration']['text'] = 'Some joinup video'; - // Almost all tests should run without the cookie consent widget. - $oe_webtools_cck_enabled = !file_exists(getcwd() . '/../disable-cookie-consent'); - $config['oe_webtools_cookie_consent.settings']['banner_popup'] = $oe_webtools_cck_enabled; - $config['oe_webtools_cookie_consent.settings']['video_popup'] = $oe_webtools_cck_enabled; // In order to ensure that tests assert dates correctly even in edge cases, we // set the website default timezone to UTC and the same in tests too so that diff --git a/tests/src/Context/FeatureContext.php b/tests/src/Context/FeatureContext.php index 6c75b00d7fb9bf4a318d2a1c373859952fc3e5e8..9ab05b765a646080198e430f37b78937cf41cdb9 100644 --- a/tests/src/Context/FeatureContext.php +++ b/tests/src/Context/FeatureContext.php @@ -22,6 +22,7 @@ use Drupal\Core\Entity\EntityChangedInterface; use Drupal\Core\Site\Settings; use Drupal\Driver\Exception\UnsupportedDriverActionException as UnsupportedDrupalDriverActionException; +use Drupal\DrupalExtension\Context\ConfigContext; use Drupal\DrupalExtension\Context\RawDrupalContext; use Drupal\DrupalExtension\TagTrait; use Drupal\image\Plugin\Field\FieldType\ImageItem; @@ -2245,39 +2246,15 @@ public function assertNoTagsInsideDiv(): void { } /** - * Disables cookie consent banner for all tests. + * Disable cookie consent for all scenarios unless tagged with @cookieConsent. * - * @BeforeSuite - */ - public static function suiteDisableCookieConsent(): void { - touch(__DIR__ . '/../../../disable-cookie-consent'); - } - - /** - * Re-enables cookie consent banner after testing. - * - * @AfterSuite - */ - public static function suiteEnableCookieConsent(): void { - @unlink(__DIR__ . '/../../../disable-cookie-consent'); - } - - /** - * Enables cookie consent banner for @cookieConsent test. - * - * @BeforeScenario @cookieConsent&&@api - */ - public function scenarioEnableCookieConsent(): void { - @unlink(__DIR__ . '/../../../disable-cookie-consent'); - } - - /** - * Disables cookie consent banner after @cookieConsent test. - * - * @AfterScenario @cookieConsent&&@api + * @BeforeScenario ~@cookieConsent&&@api */ - public function scenarioDisableCookieConsent(): void { - touch(__DIR__ . '/../../../disable-cookie-consent'); + public function disableCookieConsent(): void { + $config_context = $this->getContext(ConfigContext::class); + $name = 'oe_webtools_cookie_consent.settings'; + $config_context->setConfig($name, 'banner_popup', FALSE); + $config_context->setConfig($name, 'video_popup', FALSE); } /** diff --git a/tests/src/Context/JoinupSearchContext.php b/tests/src/Context/JoinupSearchContext.php index 49d2db9c33460accb54021f8f0aa639d55e4d6a2..efa9926dfd4fe443eb7d82cf2ad36380b32ef5e7 100644 --- a/tests/src/Context/JoinupSearchContext.php +++ b/tests/src/Context/JoinupSearchContext.php @@ -15,7 +15,6 @@ use Drupal\joinup\Traits\SearchTrait; use Drupal\joinup\Traits\TraversingTrait; use Drupal\joinup\Traits\UtilityTrait; -use Drupal\joinup_test\EventSubscriber\SearchApiFinishRequestSubscriber; use PHPUnit\Framework\Assert; /** @@ -36,24 +35,6 @@ class JoinupSearchContext extends RawDrupalContext { */ protected const SEARCH_BOX_DROPDOWN = '.joinup-navbar button#search-box-dropdown.dropdown-toggle'; - /** - * Enable search_api indexing on finish request. - * - * @BeforeScenario - */ - public function beforeScenarioFinishIndexing(): void { - \Drupal::state()->set(SearchApiFinishRequestSubscriber::STATE_KEY, TRUE); - } - - /** - * Disable search_api indexing on finish request. - * - * @AfterScenario - */ - public function afterScenarioFinishIndexing(): void { - \Drupal::state()->delete(SearchApiFinishRequestSubscriber::STATE_KEY); - } - /** * Forces the indexing of new or changed content after each step. * diff --git a/tests/src/Context/JsErrorsContext.php b/tests/src/Context/JsErrorsContext.php new file mode 100644 index 0000000000000000000000000000000000000000..aa8685de55fbbbd36dae92589eb179197e60f677 --- /dev/null +++ b/tests/src/Context/JsErrorsContext.php @@ -0,0 +1,125 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\joinup\Context; + +use Behat\Behat\Hook\Scope\AfterScenarioScope; +use Drupal\DrupalExtension\Context\RawDrupalContext; +use Symfony\Component\Yaml\Yaml; +use WebDriver\Exception\JavaScriptError; + +/** + * Context to report JS errors. + * + * Relies on js_testing_log.js injected from joinup_test. + * JS errors will throw exceptions; making behat fail. + * Drupal JS deprecations are logged in PHP (E_USER_DEPRECATED). + */ +class JsErrorsContext extends RawDrupalContext { + + /** + * Constructs a new context. + * + * @param string[]|null $ignoredErrors + * (Optional) A list of regex matching errors to ignore. + * @param string[]|null $ignoredWarnings + * (Optional) A list of regex matching warnings to ignore. + */ + public function __construct( + protected array $ignoredErrors = [], + protected array $ignoredWarnings = [], + ) {} + + /** + * Prepares state. + * + * @beforeScenario @javascript + */ + public function prepare(): void { + if (!$this->getSession()->isStarted()) { + return; + } + + // Clear the session storage. + $this->getSession()->executeScript("['js_testing_log_test.errors', 'js_testing_log_test.warnings'].forEach(key => sessionStorage.removeItem(key))"); + } + + /** + * Reports JS warnings/errors, if any. + * + * @AfterScenario ~@js-errors&&@javascript + * + * @throws \WebDriver\Exception\JavaScriptError + */ + public function report(AfterScenarioScope $scope): void { + if (!$this->getSession()->isStarted()) { + return; + } + + // Get warnings/errors and clear them from storage. + $warnings = $this->getSession()->evaluateScript("JSON.parse(sessionStorage.getItem('js_testing_log_test.warnings') || JSON.stringify([]))"); + $errors = $this->getSession()->evaluateScript("JSON.parse(sessionStorage.getItem('js_testing_log_test.errors') || JSON.stringify([]))"); + $this->getSession()->executeScript("['js_testing_log_test.errors', 'js_testing_log_test.warnings'].forEach(key => sessionStorage.removeItem(key))"); + + // Report. + $this->reportWarnings($warnings, $scope); + $this->reportErrors($errors, $scope); + } + + /** + * Reports JS warnings, if any. + */ + public function reportWarnings(array $warnings, AfterScenarioScope $scope): void { + if ($this->ignoredWarnings) { + $warnings = array_filter($warnings, fn(string $warning): bool => !$this->matchPatterns($warning, $this->ignoredWarnings)); + } + + foreach ($warnings as $warning) { + if (str_starts_with($warning, '[Deprecation]')) { + $path = sprintf("%s:%d", $scope->getFeature()->getFile(), $scope->getScenario()->getLine()); + @trigger_error(sprintf("%s\n JS deprecation: %s", $path, substr($warning, 13)), E_USER_DEPRECATED); + } + } + } + + /** + * Reports JS errors, if any. + * + * @throws \WebDriver\Exception\JavaScriptError + */ + public function reportErrors(array $errors, AfterScenarioScope $scope): void { + if ($this->ignoredErrors) { + $errors = array_filter($errors, fn(string $error): bool => !$this->matchPatterns($error, $this->ignoredErrors)); + } + + if (!$errors) { + return; + } + + $dump = Yaml::dump($errors, 30, 2, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK); + $path = sprintf("%s:%d", $scope->getFeature()->getFile(), $scope->getScenario()->getLine()); + throw new JavaScriptError(sprintf("%s\n Found %d javascript error(s)\n%s", $path, count($errors), $dump)); + } + + /** + * Check if the given string matches any of the regex patterns. + * + * @param string $subject + * The string to check patterns against. + * @param string[] $patterns + * Array of regex patterns. + * + * @return bool + * True if it matches, false otherwise. + */ + private function matchPatterns(string $subject, array $patterns): bool { + foreach ($patterns as $pattern) { + if (preg_match($pattern, $subject)) { + return TRUE; + } + } + return FALSE; + } + +} diff --git a/tests/src/Traits/AntispamTrait.php b/tests/src/Traits/AntispamTrait.php index 2a4fc8d9a734390aa8d4facab52531adf7b09d37..b1a9114bc7bf8f6fbda71def10b168b474e46bf2 100644 --- a/tests/src/Traits/AntispamTrait.php +++ b/tests/src/Traits/AntispamTrait.php @@ -4,6 +4,8 @@ namespace Drupal\joinup\Traits; +use Drupal\DrupalExtension\Context\ConfigContext; + /** * Reusable code that allows switching Antibot & Honeypot functionality on/off. */ @@ -47,32 +49,14 @@ public function assertFormIsProtectedByHoneypot(): void { } /** - * Enable the anti-spam functionalities during @antispam tests. - * - * @BeforeScenario @antispam&&@api - */ - public function enableAntispamBeforeScenarioStarts(): void { - \Drupal::state()->set('joinup_test.antispam.enabled', TRUE); - $this->clearCache(); - } - - /** - * Restores the anti-spam functionalities after @antispam tests. + * Disable anti-spam for all scenarios unless tagged with @antispam. * - * @AfterScenario @antispam&&@api + * @BeforeScenario ~@antispam&&@api */ - public function disableAntispamAfterScenarioEnds(): void { - \Drupal::state()->delete('joinup_test.antispam.enabled'); - $this->clearCache(); - } - - /** - * Clear the cache bins storing cached markup. - */ - protected function clearCache(): void { - foreach (['page', 'dynamic_page_cache', 'render'] as $bin) { - \Drupal::cache($bin)->deleteAll(); - } + public function disableAntispamBeforeScenarioStarts(): void { + $config_context = $this->getContext(ConfigContext::class); + $config_context->setConfig('antibot.settings', 'form_ids', NULL); + $config_context->setConfig('honeypot.settings', 'form_settings', []); } } diff --git a/web/modules/custom/joinup_core/tests/src/ExistingSite/ConfigTest.php b/web/modules/custom/joinup_core/tests/src/ExistingSite/ConfigTest.php index 3d7e1a2280c12a495d541c22d441ccb20bc5b409..e0c3778f9a1e0e99b58bf792de31074dcd01cab6 100644 --- a/web/modules/custom/joinup_core/tests/src/ExistingSite/ConfigTest.php +++ b/web/modules/custom/joinup_core/tests/src/ExistingSite/ConfigTest.php @@ -16,6 +16,11 @@ class ConfigTest extends JoinupExistingSiteTestBase { use DrushTestTrait; + /** + * {@inheritdoc} + */ + protected bool $disableSpamProtection = FALSE; + /** * Tests that the active and sync stores are the same. */ diff --git a/web/modules/custom/joinup_licence/js/comparator.js b/web/modules/custom/joinup_licence/js/comparator.js index 4fd4dc1a1512ad9416b6da0085c5db1783b96eef..b398dcc8f0145ca731ba43346e737ba21bebd30b 100644 --- a/web/modules/custom/joinup_licence/js/comparator.js +++ b/web/modules/custom/joinup_licence/js/comparator.js @@ -44,7 +44,6 @@ let licencesString = ""; const maxCompare = 5; const licencesArray = []; - const licenceListing = document.querySelector(".listing--licences"); let licenceName = ""; document .querySelectorAll(".licence-tile .form-check-input[type='checkbox']") @@ -61,10 +60,14 @@ } } }); - licenceListing.setAttribute( - "data-joinup-licence-compare", - licencesString, - ); + + const licenceListing = document.querySelector(".listing--licences"); + if (licenceListing) { + licenceListing.setAttribute( + "data-joinup-licence-compare", + licencesString, + ); + } } // Reset licence listing. diff --git a/web/modules/custom/joinup_licence/js/filter.js b/web/modules/custom/joinup_licence/js/filter.js index a32784c84bc6a47e9eb2b70716e53a6582b2e041..303e3742a8145912bc26509dd1545c2e6aa4c09a 100644 --- a/web/modules/custom/joinup_licence/js/filter.js +++ b/web/modules/custom/joinup_licence/js/filter.js @@ -80,16 +80,17 @@ const licenceCounterNumber = document.querySelector( ".licence-counter__number", ); - licenceCounterNumber.textContent = licenceTiles; + if (licenceCounterNumber) { + licenceCounterNumber.textContent = licenceTiles; + } // Use plural only if licences count is higher than one. const licenceCounterText = document.querySelector( ".licence-counter__text", ); - if (licenceTiles === 1) { - licenceCounterText.textContent = "licence found"; - } else { - licenceCounterText.textContent = "licences found"; + if (licenceCounterText) { + licenceCounterText.textContent = + licenceTiles === 1 ? "licence found" : "licences found"; } } diff --git a/web/modules/custom/joinup_test/config/install/joinup_test.settings.yml b/web/modules/custom/joinup_test/config/install/joinup_test.settings.yml new file mode 100644 index 0000000000000000000000000000000000000000..e3d839dfe6de056796850fb0f6ab39acdbcc2d48 --- /dev/null +++ b/web/modules/custom/joinup_test/config/install/joinup_test.settings.yml @@ -0,0 +1 @@ +test_mode: FALSE diff --git a/web/modules/custom/joinup_test/config/schema/joinup_test.schema.yml b/web/modules/custom/joinup_test/config/schema/joinup_test.schema.yml new file mode 100644 index 0000000000000000000000000000000000000000..02110d6238b3b5be363c746de7082ae50f8a8d00 --- /dev/null +++ b/web/modules/custom/joinup_test/config/schema/joinup_test.schema.yml @@ -0,0 +1,5 @@ +joinup_test.settings: + type: config_object + mapping: + test_mode: + type: boolean diff --git a/web/modules/custom/joinup_test/joinup_test.libraries.yml b/web/modules/custom/joinup_test/joinup_test.libraries.yml new file mode 100644 index 0000000000000000000000000000000000000000..1bbfe411076c573213060fc036b53bb9c0d07878 --- /dev/null +++ b/web/modules/custom/joinup_test/joinup_test.libraries.yml @@ -0,0 +1,13 @@ +# Relies on core js_testing_log_test. +testing.js_errors_log: + header: true + js: + /core/modules/system/tests/modules/js_testing_log_test/js/js_testing_log.js: { weight: -1000 } + +# Relies on core css_disable_transitions_test. +testing.css_disable_transitions: + css: + theme: + /core/modules/system/tests/modules/css_disable_transitions_test/css/disable_transitions.theme.css: { weight: 100 } + js: + /core/modules/system/tests/modules/css_disable_transitions_test/js/disable_transitions.theme.js: { } diff --git a/web/modules/custom/joinup_test/joinup_test.module b/web/modules/custom/joinup_test/joinup_test.module index 9fdac42cbd22c96b40bca01e353273d45427913b..2119489a3c0d02cf111a85ad080d27dce975bbf4 100644 --- a/web/modules/custom/joinup_test/joinup_test.module +++ b/web/modules/custom/joinup_test/joinup_test.module @@ -7,13 +7,40 @@ declare(strict_types=1); +use Drupal\Core\Cache\CacheableMetadata; +use Drupal\joinup_test\Utils\TestUtils; + +/** + * Implements hook_page_attachments(). + */ +function joinup_test_page_attachments(array &$attachments): void { + // Behat mode: cache metadata. + $settings = \Drupal::config('joinup_test.settings'); + CacheableMetadata::createFromRenderArray($attachments) + ->addCacheableDependency($settings) + ->applyTo($attachments); + + if (TestUtils::isTestMode()) { + // Attach JS errors collector and disable CSS animations. + $attachments['#attached']['library'][] = 'joinup_test/testing.js_errors_log'; + $attachments['#attached']['library'][] = 'joinup_test/testing.css_disable_transitions'; + } +} + +/** + * Implements hook_js_settings_alter(). + */ +function joinup_test_js_settings_alter(&$settings): void { + // Output JS deprecation warnings. + $settings['suppressDeprecationErrors'] = FALSE; +} + /** * Implements hook_config_schema_info_alter(). */ function joinup_test_config_schema_info_alter(&$definitions): void { if (isset($definitions['antibot.settings']['mapping']['form_ids'])) { // Required to pass config_inspector validation. - /* @see \Drupal\joinup_test\Config\JoinupTestConfigOverrider::loadOverrides */ $definitions['antibot.settings']['mapping']['form_ids']['nullable'] = TRUE; } } @@ -22,8 +49,10 @@ function joinup_test_config_schema_info_alter(&$definitions): void { * Implements hook_honeypot_form_protections_alter(). */ function joinup_test_honeypot_form_protections_alter(array &$options, array $form): void { - // Allow to disable anti-spam tools. - if (!\Drupal::state()->get('joinup_test.antispam.enabled', FALSE)) { + // Disable Honeypot entirely if form_settings is empty, used in behat. + /* @see \Drupal\joinup\Traits\AntispamTrait::disableAntispamBeforeScenarioStarts */ + $honeypot_settings = \Drupal::config('honeypot.settings'); + if (!$honeypot_settings->get('form_settings')) { $options = []; } } diff --git a/web/modules/custom/joinup_test/joinup_test.services.yml b/web/modules/custom/joinup_test/joinup_test.services.yml index 84ee5e54555bbec7d2c8551ba9d094aa45e71fa8..25172d0c121acceefeb3ca7fd17689bcacacde65 100644 --- a/web/modules/custom/joinup_test/joinup_test.services.yml +++ b/web/modules/custom/joinup_test/joinup_test.services.yml @@ -1,11 +1,6 @@ services: - joinup_test.config_overrider: - class: Drupal\joinup_test\Config\JoinupTestConfigOverrider - arguments: ['@state', '@config.factory'] - tags: - - { name: config.factory.override } joinup_test.search_api_finish_request_subscriber: class: Drupal\joinup_test\EventSubscriber\SearchApiFinishRequestSubscriber - arguments: ['@state', '@search_api.post_request_indexing'] + arguments: ['@search_api.post_request_indexing'] tags: - { name: event_subscriber } diff --git a/web/modules/custom/joinup_test/src/Config/JoinupTestConfigOverrider.php b/web/modules/custom/joinup_test/src/Config/JoinupTestConfigOverrider.php deleted file mode 100644 index b07f13fb16c00709fd0226045b378c46d908d921..0000000000000000000000000000000000000000 --- a/web/modules/custom/joinup_test/src/Config/JoinupTestConfigOverrider.php +++ /dev/null @@ -1,75 +0,0 @@ -<?php - -declare(strict_types=1); - -namespace Drupal\joinup_test\Config; - -use Drupal\Core\Cache\CacheableMetadata; -use Drupal\Core\Config\ConfigFactoryInterface; -use Drupal\Core\Config\ConfigFactoryOverrideInterface; -use Drupal\Core\Config\StorableConfigBase; -use Drupal\Core\Config\StorageInterface; -use Drupal\Core\State\StateInterface; - -/** - * Config overrider use to disable anti-spam guard. - */ -class JoinupTestConfigOverrider implements ConfigFactoryOverrideInterface { - - /** - * Constructs a new config overrider service instance. - * - * @param \Drupal\Core\State\StateInterface $state - * The state service. - * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory - * The configuration factory service. - */ - public function __construct( - protected StateInterface $state, - protected ConfigFactoryInterface $configFactory, - ) { - } - - /** - * {@inheritdoc} - */ - public function loadOverrides($names): array { - if (in_array('antibot.settings', $names, TRUE) && !$this->state->get('joinup_test.antispam.enabled', FALSE)) { - return [ - 'antibot.settings' => [ - // We cannot set an empty array as the indexed arrays are merged, and - // we'll get again the list from storage. - // @see https://www.drupal.org/project/drupal/issues/2990549 - 'form_ids' => NULL, - ], - ]; - } - return []; - } - - /** - * {@inheritdoc} - */ - public function getCacheSuffix(): string { - return 'joinup_core'; - } - - /** - * {@inheritdoc} - */ - public function createConfigObject($name, $collection = StorageInterface::DEFAULT_COLLECTION): ?StorableConfigBase { - return NULL; - } - - /** - * {@inheritdoc} - */ - public function getCacheableMetadata($name): CacheableMetadata { - $metadata = new CacheableMetadata(); - if ($name === 'antibot.settings') { - $metadata->addCacheableDependency($this->configFactory->get($name)); - } - return $metadata; - } - -} diff --git a/web/modules/custom/joinup_test/src/EventSubscriber/SearchApiFinishRequestSubscriber.php b/web/modules/custom/joinup_test/src/EventSubscriber/SearchApiFinishRequestSubscriber.php index a30f162f74aea2d74dcf29acfd43b568d9c86ab2..1810adc9120fd0d083aaf4933589972ebdd96aa0 100644 --- a/web/modules/custom/joinup_test/src/EventSubscriber/SearchApiFinishRequestSubscriber.php +++ b/web/modules/custom/joinup_test/src/EventSubscriber/SearchApiFinishRequestSubscriber.php @@ -4,7 +4,7 @@ namespace Drupal\joinup_test\EventSubscriber; -use Drupal\Core\State\StateInterface; +use Drupal\joinup_test\Utils\TestUtils; use Drupal\search_api\Utility\PostRequestIndexing; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\KernelEvents; @@ -21,17 +21,14 @@ */ class SearchApiFinishRequestSubscriber implements EventSubscriberInterface { - const STATE_KEY = 'joinup_test.index_on_finish_request'; - - public function __construct(protected StateInterface $state, protected PostRequestIndexing $postRequestIndexing) { + public function __construct(protected PostRequestIndexing $postRequestIndexing) { } /** * Indexes items on FINISH_REQUEST, if specified. */ public function onKernelFinishRequest(): void { - $is_active = $this->state->get(self::STATE_KEY, FALSE); - if ($is_active) { + if (TestUtils::isTestMode()) { $this->postRequestIndexing->destruct(); } } diff --git a/web/modules/custom/joinup_test/src/Utils/TestUtils.php b/web/modules/custom/joinup_test/src/Utils/TestUtils.php new file mode 100644 index 0000000000000000000000000000000000000000..11b8ab79e321160624ebeb8d14381a8d28bdd06b --- /dev/null +++ b/web/modules/custom/joinup_test/src/Utils/TestUtils.php @@ -0,0 +1,19 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\joinup_test\Utils; + +/** + * Contains utility methods. + */ +class TestUtils { + + /** + * Whether we are in test mode or not. + */ + public static function isTestMode(): bool { + return (bool) \Drupal::config('joinup_test.settings')->get('test_mode'); + } + +} diff --git a/web/modules/custom/joinup_test/tests/src/ExistingSite/JoinupExistingSiteTestBase.php b/web/modules/custom/joinup_test/tests/src/ExistingSite/JoinupExistingSiteTestBase.php index 7a0eee15fec3122ed0fda0b73c5759999b83bee9..fa20ee2d0e6461fac1b3a785b8a0cffe09d448e3 100644 --- a/web/modules/custom/joinup_test/tests/src/ExistingSite/JoinupExistingSiteTestBase.php +++ b/web/modules/custom/joinup_test/tests/src/ExistingSite/JoinupExistingSiteTestBase.php @@ -7,6 +7,7 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\Url; +use Drupal\Tests\joinup_test\Traits\ConfigTestTrait; use Drupal\Tests\joinup_test\Traits\RdfEntityCreationTrait; use Drupal\taxonomy\VocabularyInterface; use weitzman\DrupalTestTraits\ExistingSiteBase; @@ -25,6 +26,12 @@ abstract class JoinupExistingSiteTestBase extends ExistingSiteBase { */ protected array $casUsers = []; + /** + * Whether to disable the spam protection. + */ + protected bool $disableSpamProtection = TRUE; + + use ConfigTestTrait; use RdfEntityCreationTrait; /** @@ -34,17 +41,17 @@ protected function setUp(): void { parent::setUp(); // Disable the spam protection functionality. - \Drupal::state()->delete('joinup_test.antispam.enabled'); - $this->clearCache(); + if ($this->disableSpamProtection) { + $this->overrideConfig('antibot.settings', 'form_ids', NULL); + $this->overrideConfig('honeypot.settings', 'form_settings', []); + } } /** * {@inheritdoc} */ public function tearDown(): void { - // Restores the spam protection functionality. - \Drupal::state()->set('joinup_test.antispam.enabled', TRUE); - $this->clearCache(); + $this->restoreOverriddenConfig(); // Avoid sending notifications during test entities cleanup. First, sort the // entities by moving the 'user' entities at the end, as deleting a user @@ -79,15 +86,6 @@ public function tearDown(): void { $delete_orphans_plugin->process(); } - /** - * Clear the cache bins storing cached markup. - */ - protected function clearCache(): void { - foreach (['page', 'dynamic_page_cache', 'render'] as $bin) { - \Drupal::cache($bin)->deleteAll(); - } - } - /** * {@inheritdoc} */ diff --git a/web/modules/custom/joinup_test/tests/src/Traits/ConfigTestTrait.php b/web/modules/custom/joinup_test/tests/src/Traits/ConfigTestTrait.php index 54b1bd9a5686281b9676ac690307d4cfb21fd67c..8d5821a0296569253365f1c09e4451565036ef38 100644 --- a/web/modules/custom/joinup_test/tests/src/Traits/ConfigTestTrait.php +++ b/web/modules/custom/joinup_test/tests/src/Traits/ConfigTestTrait.php @@ -5,12 +5,27 @@ namespace Drupal\Tests\joinup_test\Traits; use Drupal\Component\Serialization\Yaml; +use Drupal\Core\Config\ConfigFactoryInterface; /** * Config reusable testing helper methods. */ trait ConfigTestTrait { + /** + * Config factory under test. + * + * @var \Drupal\Core\Config\ConfigFactory + */ + protected $configFactory; + + /** + * Keep track of any config that was changed, so they can easily be reverted. + * + * @var array + */ + protected array $originalConfig = []; + /** * Returns config sync configuration data. * @@ -49,4 +64,55 @@ protected function importConfigs(array $config_names): void { } } + /** + * Sets a value in a configuration object. + * + * @param string $name + * The name of the configuration object. + * @param string $key + * Identifier to store value in configuration. + * @param mixed $value + * Value to associate with identifier. + */ + protected function overrideConfig(string $name, string $key, mixed $value): void { + $config = $this->getConfigFactory()->getEditable($name); + $original = $config->get($key); + $config->set($key, $value)->save(); + if (!array_key_exists($name, $this->originalConfig)) { + $this->originalConfig[$name][$key] = $original; + return; + } + + if (!array_key_exists($key, $this->originalConfig[$name])) { + $this->originalConfig[$name][$key] = $original; + } + } + + /** + * Restores original config. + */ + protected function restoreOverriddenConfig(): void { + if (!$this->originalConfig) { + // Nothing to restore. + return; + } + + foreach ($this->originalConfig as $name => $key_value) { + foreach ($key_value as $key => $value) { + $this->getConfigFactory()->getEditable($name)->set($key, $value)->save(); + } + } + $this->originalConfig = []; + } + + /** + * Get the config factory. + */ + protected function getConfigFactory(): ConfigFactoryInterface { + if (!isset($this->configFactory)) { + $this->configFactory = $this->container->get('config.factory'); + } + return $this->configFactory; + } + } diff --git a/web/themes/ventuno/assets/js/fieldset-show-more.min.js b/web/themes/ventuno/assets/js/fieldset-show-more.min.js index 1db95e9035112b7e5d18a91dd39bccd652e76be2..a8fa01a31cec2e456ab1f00c594a8fa49ae339b4 100644 --- a/web/themes/ventuno/assets/js/fieldset-show-more.min.js +++ b/web/themes/ventuno/assets/js/fieldset-show-more.min.js @@ -1,2 +1,2 @@ -!function(e){"function"==typeof define&&define.amd?define(e):e()}((function(){"use strict";((e,t)=>{e.behaviors.fieldsetShowMore={attach(o){t("fieldset-show-more",document.querySelectorAll(".fieldset-show-more"),o).forEach((t=>{const o=t.querySelector(".fieldset-show-more__btn"),n=o.querySelector(".fieldset-show-more__btn-text"),i=e.t("Show more"),r=e.t("Show less");null!=o&&void 0!==n&&void 0!==n&&o.addEventListener("click",(function(){"true"===o.getAttribute("aria-expanded")?n.textContent=r:n.textContent=i}))}))}}})(Drupal,once)})); +!function(e){"function"==typeof define&&define.amd?define(e):e()}((function(){"use strict";((e,t)=>{e.behaviors.fieldsetShowMore={attach(o){t("fieldset-show-more",document.querySelectorAll(".fieldset-show-more"),o).forEach((t=>{const o=t.querySelector(".fieldset-show-more__btn"),n=o?o.querySelector(".fieldset-show-more__btn-text"):null;o&&n&&o.addEventListener("click",(()=>{n.textContent="true"===o.getAttribute("aria-expanded")?e.t("Show less"):e.t("Show more")}))}))}}})(Drupal,once)})); //# sourceMappingURL=fieldset-show-more.min.js.map diff --git a/web/themes/ventuno/assets/js/sticky-groupheader.min.js b/web/themes/ventuno/assets/js/sticky-groupheader.min.js index 4df748a0cf50779a1bce60ca26d430aa9a9e89de..51a858a730b016e99131661db7be81a6ca7c42f0 100644 --- a/web/themes/ventuno/assets/js/sticky-groupheader.min.js +++ b/web/themes/ventuno/assets/js/sticky-groupheader.min.js @@ -1,2 +1,2 @@ -!function(e){"function"==typeof define&&define.amd?define(e):e()}((function(){"use strict";((e,t,s,r)=>{const i="#block-ventuno-groupheader",n=".region-header",o=".region-header__inner",h=".joinup-navbar",a="html";class c{constructor(e){this.navbar=e.querySelector(h),this.header=e.querySelector(n),this.headerInner=e.querySelector(o),this.html=e.querySelector(a),this.scrollPT=0,this.isSticky=!1,this.observer=null}init(){this.navbar&&this.header&&this.headerInner&&this.html&&(this.scrollPT=parseInt(this.html.style.scrollPaddingTop,10)||0,this.navbar.classList.remove("navbar--sticky"),this.navbar.classList.add("position-static"),this.updateObserver())}updateObserver(){this.observer&&(this.observer.disconnect(),this.observer=null),this.observer=new IntersectionObserver((e=>{e.forEach((e=>{this.onIntersect(!e.isIntersecting)}))}),{rootMargin:`-${t.offsets.top}px 0px 0px -${t.offsets.left}px`}),this.observer.observe(this.navbar)}onIntersect(e){if(this.isSticky!==e){const s=!this.isSticky&&e,r=this.headerInner.offsetHeight;this.header.style.height=s?`${r}px`:null;const i=s?t.offsets.top+r:0;this.html.style.scrollPaddingTop=`${this.scrollPT+i}px`,this.headerInner.classList.toggle("fixed-top",s),this.isSticky=e}}}e.behaviors.stickyGroupHeader={attach(e){s("sticky-group-header",i,e).forEach((()=>{const t=new c(e);t.init(),r(e).on("drupalViewportOffsetChange",(()=>{t.updateObserver()}))}))}}})(Drupal,Drupal.displace,once,jQuery)})); +!function(e){"function"==typeof define&&define.amd?define(e):e()}((function(){"use strict";((e,t,s,r)=>{const i="#block-ventuno-groupheader",n=".region-header",o=".region-header__inner",h=".joinup-navbar",a="html";class c{constructor(e){this.context=e,this.navbar=e.querySelector(h),this.header=e.querySelector(n),this.headerInner=e.querySelector(o),this.html=e.querySelector(a),this.scrollPT=0,this.isSticky=!1,this.observer=null}init(){this.navbar&&this.header&&this.headerInner&&this.html&&(this.scrollPT=parseInt(this.html.style.scrollPaddingTop,10)||0,this.navbar.classList.remove("navbar--sticky"),this.navbar.classList.add("position-static"),this.updateObserver(),r(this.context).on("drupalViewportOffsetChange",(()=>{this.updateObserver()})))}updateObserver(){this.observer&&(this.observer.disconnect(),this.observer=null),this.observer=new IntersectionObserver((e=>{e.forEach((e=>{this.onIntersect(!e.isIntersecting)}))}),{rootMargin:`-${t.offsets.top}px 0px 0px -${t.offsets.left}px`}),this.observer.observe(this.navbar)}onIntersect(e){if(this.isSticky!==e){const s=!this.isSticky&&e,r=this.headerInner.offsetHeight;this.header.style.height=s?`${r}px`:null;const i=s?t.offsets.top+r:0;this.html.style.scrollPaddingTop=`${this.scrollPT+i}px`,this.headerInner.classList.toggle("fixed-top",s),this.isSticky=e}}}e.behaviors.stickyGroupHeader={attach(e){s("sticky-group-header",i,e).forEach((()=>{new c(e).init()}))}}})(Drupal,Drupal.displace,once,jQuery)})); //# sourceMappingURL=sticky-groupheader.min.js.map diff --git a/web/themes/ventuno/src/js/fieldset-show-more.js b/web/themes/ventuno/src/js/fieldset-show-more.js index ce4bf9bcdedc73572a9a7e4baaf95a03c17954d5..c72b2030a1a92e45dff2c7fd28ff102db0a7266f 100644 --- a/web/themes/ventuno/src/js/fieldset-show-more.js +++ b/web/themes/ventuno/src/js/fieldset-show-more.js @@ -15,26 +15,19 @@ // Ensures "Show more" functionality in case there are multiple fieldsets fieldsets.forEach((fieldset) => { const btn = fieldset.querySelector(".fieldset-show-more__btn"); - const btnText = btn.querySelector(".fieldset-show-more__btn-text"); - const textMore = Drupal.t("Show more"); - const textLess = Drupal.t("Show less"); - function updateContent() { - // Uses "aria-expanded" attribute to know the state of the collapse - if (btn.getAttribute("aria-expanded") === "true") { - btnText.textContent = textLess; - } else { - btnText.textContent = textMore; - } - } - if ( - btn === null || - btn === undefined || - btnText === undefined || - btnText === undefined - ) { - return; + const btnText = btn + ? btn.querySelector(".fieldset-show-more__btn-text") + : null; + + if (btn && btnText) { + btn.addEventListener("click", () => { + // Uses "aria-expanded" attribute to know the state of the collapse + btnText.textContent = + btn.getAttribute("aria-expanded") === "true" + ? Drupal.t("Show less") + : Drupal.t("Show more"); + }); } - btn.addEventListener("click", updateContent); }); }, }; diff --git a/web/themes/ventuno/src/js/sticky-groupheader.js b/web/themes/ventuno/src/js/sticky-groupheader.js index f4ac8da4c5ca7a0f9dcfccd8d051cdc7fc7fe5fa..7f7a0952862d090420d0180bb59d901612d04bb2 100644 --- a/web/themes/ventuno/src/js/sticky-groupheader.js +++ b/web/themes/ventuno/src/js/sticky-groupheader.js @@ -22,6 +22,7 @@ class StickyGroupHeader { constructor(context) { + this.context = context; this.navbar = context.querySelector(SELECTORS.NAVBAR); this.header = context.querySelector(SELECTORS.HEADER); this.headerInner = context.querySelector(SELECTORS.HEADER_INNER); @@ -46,6 +47,12 @@ // Init the observer. this.updateObserver(); + + // Refresh the observer when Drupal.displace offsets change, for new + // rootMargin's to take effect. + $(this.context).on("drupalViewportOffsetChange", () => { + this.updateObserver(); + }); } /** @@ -108,12 +115,6 @@ () => { const stickyHeader = new StickyGroupHeader(context); stickyHeader.init(); - - // Refresh the observer when Drupal.displace offsets change, for new - // rootMargin's to take effect. - $(context).on("drupalViewportOffsetChange", () => { - stickyHeader.updateObserver(); - }); }, ); },