diff --git a/core/misc/collapse.js b/core/misc/collapse.js index 767325e..1348d8e 100644 --- a/core/misc/collapse.js +++ b/core/misc/collapse.js @@ -140,6 +140,32 @@ } }; + /** + * Show parent details elements or vertical tab pane of a fragment target. + * + * @param {jQuery.Event} e + * The event triggered. + */ + function showGroupingElementParentOfFragmentTarget(e) { + var $item = (e.type === 'click') ? $(e.currentTarget.hash) : $('#' + location.hash.substr(1)); + + if ($item.length) { + // Open all (nested) parent details and set aria attributes on the summary + // by triggering the click event listener in details-aria.js. + $item.parents('details').not('[open]').find('> summary').trigger('click.detailsAria'); + + // Loop over all (nested) parent vertical tabs and focus them. + $item.parents('.vertical-tabs__pane').each(function(index, pane) { + $(pane).data('verticalTab').focus(); + }); + } + } + + // Open or focus a vertical tab or details element on a hash change or + // fragment link click when the target is a child element. + $(document).on('click.show-grouping-fragment-target', 'a[href^="#"]', showGroupingElementParentOfFragmentTarget); + $(window).on('hashchange.show-grouping-fragment-target', showGroupingElementParentOfFragmentTarget); + // Expose constructor in the public space. Drupal.CollapsibleDetails = CollapsibleDetails; diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Core/Form/FormGroupingElementsTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Core/Form/FormGroupingElementsTest.php new file mode 100644 index 0000000..8dbaaa1 --- /dev/null +++ b/core/tests/Drupal/FunctionalJavascriptTests/Core/Form/FormGroupingElementsTest.php @@ -0,0 +1,133 @@ +drupalCreateUser(); + $this->drupalLogin($account); + } + + /** + * Tests that vertical tab children become visible. + * + * Makes sure that a child element of a vertical tab that is not visible, + * becomes visible when the tab is clicked, a fragment link to the child is + * clicked or when the URI fragment pointing to that child changes. + */ + public function testVerticalTabChildVisibility() { + $session = $this->getSession(); + + // Request the group vertical tabs testing page with a fragment identifier + // to the second element. + $this->drupalGet('form-test/group-vertical-tabs', ['fragment' => 'edit-element-2']); + + $page = $session->getPage(); + + $tab_link_1 = $page->find('css', '.vertical-tabs__menu-item > a'); + + $child_1_selector = '#edit-element'; + $child_1 = $page->find('css', $child_1_selector); + + $child_2_selector = '#edit-element-2'; + $child_2 = $page->find('css', $child_2_selector); + + // Assert that the child in the second vertical tab becomes visible. + // It should be visible after initial load due to the fragment in the URI. + $this->assertTrue($child_2->isVisible(), 'Child 2 is visible due to a URI fragment'); + + // Click on a fragment link pointing to an invisible child inside an + // inactive vertical tab. + $session->executeScript("jQuery('').insertAfter('h1')[0].click()"); + + // Assert that the child in the first vertical tab is visible. + $this->assertTrue($child_1->isVisible(), 'Child 1 is visible after a fragment link click'); + + // Trigger a URI fragment change (hashchange) to show the second vertical + // tab again. + $session->executeScript("location.replace('$child_2_selector')"); + + // Assert that the child in the second vertical tab is visible again. + $this->assertTrue($child_2->isVisible(), 'Child 2 is visible after a fragment change'); + + $tab_link_1->click(); + + // Assert that the child in the first vertical tab is visible again after + // a click on the first tab. + $this->assertTrue($child_1->isVisible(), 'Child 1 is visible after clicking the parent tab'); + } + + /** + * Tests that details element children become visible. + * + * Makes sure that a child element of a details element that is not visible, + * becomes visible when a fragment link to the child is clicked or when the + * URI fragment pointing to that child changes. + */ + public function testDetailsChildVisibility() { + $session = $this->getSession(); + + // Store reusable JavaScript code to remove the current URI fragment and + // close all details. + $reset_js = "location.replace('#'); jQuery('details').removeAttr('open')"; + + // Request the group details testing page. + $this->drupalGet('form-test/group-details'); + + $page = $session->getPage(); + + $session->executeScript($reset_js); + + $child_selector = '#edit-element'; + $child = $page->find('css', $child_selector); + + // Assert that the child is not visible. + $this->assertFalse($child->isVisible(), 'Child is not visible'); + + // Trigger a URI fragment change (hashchange) to open all parent details + // elements of the child. + $session->executeScript("location.replace('$child_selector')"); + + // Assert that the child is visible again. + $this->assertTrue($child->isVisible(), 'Child became visible after a fragment change'); + + $this->createScreenshot('/tmp/drupal/wel.jpg'); + + $session->executeScript($reset_js); + + // Click on a fragment link pointing to an invisible child inside a closed + // details element. + $session->executeScript("jQuery('').insertAfter('h1')[0].click()"); + + // Assert that the child is visible again. + $this->assertTrue($child->isVisible(), 'Child became visible after a fragment link click'); + + // Find the summary belonging to the closest details element. + $summary = $page->find('css', '#edit-meta > summary'); + + // Assert that both aria-expanded and aria-pressed are true. + $this->assertTrue($summary->getAttribute('aria-expanded')); + $this->assertTrue($summary->getAttribute('aria-pressed')); + } + +}