diff --git a/src/utils/isElementVisible.ts b/src/utils/isElementVisible.ts index 27aa0cfd4..ba7d00e22 100644 --- a/src/utils/isElementVisible.ts +++ b/src/utils/isElementVisible.ts @@ -19,18 +19,47 @@ function isStyleVisible(element: T) { ) } -function isAttributeVisible(element: T) { - return ( - !element.hasAttribute('hidden') && - (element.nodeName === 'DETAILS' ? element.hasAttribute('open') : true) - ) +/** + * checks if an element is visible by checking its attributes and its parents' attributes. + * @param element + * @returns boolean + */ +function isElementVisibleByAttribute(element: T) { + if (element instanceof HTMLInputElement && (element.type === 'hidden' || element.hidden)) { + return false + } + return true +} + +/** + * checks if an element is hidden by a closed details element. + * @param element + * @returns boolean + */ +function isHiddenByClosedDetails(element: T) { + const summary = element.closest('summary') + let parent = element.parentElement + + while (parent) { + if (parent.nodeName === 'DETAILS' && !(parent as HTMLDetailsElement).open) { + // If no ancestor exists, or the is not a direct child of this
, + // then the content is hidden + if (!summary || summary.parentElement !== parent) { + return true + } + } + parent = parent.parentElement + } + + return false } export function isElementVisible(element: T): boolean { return ( element.nodeName !== '#comment' && isStyleVisible(element) && - isAttributeVisible(element) && + isElementVisibleByAttribute(element) && + !isHiddenByClosedDetails(element) && (!element.parentElement || isElementVisible(element.parentElement)) ) } diff --git a/tests/isVisible.spec.ts b/tests/isVisible.spec.ts index 2de407a61..61bbaaadc 100644 --- a/tests/isVisible.spec.ts +++ b/tests/isVisible.spec.ts @@ -229,5 +229,104 @@ describe('isVisible', () => { expect(wrapper.isVisible()).toBe(false) }) }) + + describe('details and summary elements', () => { + it('DetailContent should be invisible when display:none is applied by class', () => { + const style = document.createElement('style') + document.head.appendChild(style) + style.sheet!.insertRule('.hidden { display: none; }') + + const wrapper = mount({ + template: '' + }) + + expect(wrapper.get('#my-div').isVisible()).toBe(false) + expect(wrapper.find('summary').isVisible()).toBe(false) + expect(wrapper.find('details').isVisible()).toBe(false) + }) + it('DetailContent should be invisible when display:none is applied by style attribute', () => { + const wrapper = mount({ + template: '' + }) + + expect(wrapper.get('#my-div').isVisible()).toBe(false) + expect(wrapper.find('summary').isVisible()).toBe(false) + expect(wrapper.find('details').isVisible()).toBe(false) + }) + it('DetailContent should be visible when summary is visible', () => { + const DetailContent = defineComponent({ + template: `
Summary
Content
` + }) + + const wrapper = mount(DetailContent) + expect(wrapper.find('details').isVisible()).toBe(true) + expect(wrapper.find('summary').isVisible()).toBe(true) + expect(wrapper.find('div').isVisible()).toBe(false) + }) + it ('DetailContent shouild be visible when summarys child is visible', () => { + const childContent = defineComponent({ + template: `
Summary
` + }) + const wrapper = mount(childContent) + expect(wrapper.find('summary span').isVisible()).toBe(true); + }) + it('should consider a summary as hidden when nested inside closed details content', () => { + const NestedSummaryInClosedDetails = defineComponent({ + template: ` +
+ Main summary +
+
+ Nested summary +
+
+
+ ` + }) + const wrapper = mount(NestedSummaryInClosedDetails) + const summaries = wrapper.findAll('summary') + + expect(summaries[0].isVisible()).toBe(true) + expect(summaries[1].isVisible()).toBe(false) + }) + it('should consider a summary as visible when nested inside open details content', () => { + const NestedSummaryInOpenDetails = defineComponent({ + template: ` +
+ Main summary +
+
+ Nested summary +
+
+
+ ` + }) + const wrapper = mount(NestedSummaryInOpenDetails) + const summaries = wrapper.findAll('summary') + + expect(summaries[0].isVisible()).toBe(true) + expect(summaries[1].isVisible()).toBe(true) + }) + it('should consider a 1st summary as visible and 2nd as hidden when nested inside closed details content which is applied display: none', () => { + const NestedSummaryInOpenDetails = defineComponent({ + template: ` +
+ Main summary +
+
+ Nested summary +
+
+
+ ` + }) + const wrapper = mount(NestedSummaryInOpenDetails) + const summaries = wrapper.findAll('summary') + + expect(summaries[0].isVisible()).toBe(true) + expect(summaries[1].isVisible()).toBe(false) + }) + }) }) })