diff --git a/packages/happy-dom/src/nodes/html-details-element/HTMLDetailsElement.ts b/packages/happy-dom/src/nodes/html-details-element/HTMLDetailsElement.ts index c5b9cc561..815c768df 100644 --- a/packages/happy-dom/src/nodes/html-details-element/HTMLDetailsElement.ts +++ b/packages/happy-dom/src/nodes/html-details-element/HTMLDetailsElement.ts @@ -2,6 +2,8 @@ import Event from '../../event/Event.js'; import HTMLElement from '../html-element/HTMLElement.js'; import * as PropertySymbol from '../../PropertySymbol.js'; import Attr from '../attr/Attr.js'; +import EventPhaseEnum from '../../event/EventPhaseEnum.js'; +import MouseEvent from '../../event/events/MouseEvent.js'; /** * HTMLDetailsElement @@ -56,4 +58,23 @@ export default class HTMLDetailsElement extends HTMLElement { this.dispatchEvent(new Event('toggle')); } } + + /** + * @override + */ + public override dispatchEvent(event: Event): boolean { + const returnValue = super.dispatchEvent(event); + + if ( + !event[PropertySymbol.defaultPrevented] && + event[PropertySymbol.target]?.[PropertySymbol.localName] === 'summary' && + event.type === 'click' && + event.eventPhase === EventPhaseEnum.bubbling && + event instanceof MouseEvent + ) { + this.open = !this.open; + } + + return returnValue; + } } diff --git a/packages/happy-dom/test/nodes/html-details-element/HTMLDetailsElement.test.ts b/packages/happy-dom/test/nodes/html-details-element/HTMLDetailsElement.test.ts index 0d1eb5925..16f2398aa 100644 --- a/packages/happy-dom/test/nodes/html-details-element/HTMLDetailsElement.test.ts +++ b/packages/happy-dom/test/nodes/html-details-element/HTMLDetailsElement.test.ts @@ -3,6 +3,7 @@ import Window from '../../../src/window/Window.js'; import Document from '../../../src/nodes/document/Document.js'; import { beforeEach, describe, it, expect, vi } from 'vitest'; import Event from '../../../src/event/Event.js'; +import MouseEvent from '../../../src/event/events/MouseEvent.js'; describe('HTMLDetailsElement', () => { let window: Window; @@ -51,5 +52,21 @@ describe('HTMLDetailsElement', () => { element.open = false; expect(((triggeredEvent)).type).toBe('toggle'); }); + + it('Should not toggle the open state when a click event is dispatched directly on the details element', () => { + element.dispatchEvent(new MouseEvent('click')); + expect(element.open).toBe(false); + }); + + it('Should toggle the "open" attribute when a click event is dispatched on a summary element, which is a child of the details element', () => { + const summary = document.createElement('summary'); + element.appendChild(summary); + + summary.click(); + expect(element.open).toBe(true); + + summary.click(); + expect(element.open).toBe(false); + }); }); }); diff --git a/packages/jest-environment/test/testing-library/TestingLibrary.test.tsx b/packages/jest-environment/test/testing-library/TestingLibrary.test.tsx index 1c530ad74..17fa1bd08 100644 --- a/packages/jest-environment/test/testing-library/TestingLibrary.test.tsx +++ b/packages/jest-environment/test/testing-library/TestingLibrary.test.tsx @@ -78,4 +78,34 @@ describe('TestingLibrary', () => { expect(attribute).toMatch('test'); expect(attribute).not.toMatch('hello'); }); + + it('Removes and adds `open` attribute for `
` element on click event.', async () => { + const user = UserEvent.setup(); + const handleToggle = jest.fn(); + + render( +
+ summarydetails +
+ ); + + expect(document.body.innerHTML).toBe( + '
summarydetails
' + ); + expect(handleToggle).toHaveBeenCalledTimes(0); + + await user.click(screen.getByText('summary')); + + expect(document.body.innerHTML).toBe( + '
summarydetails
' + ); + expect(handleToggle).toHaveBeenCalledTimes(1); + + await user.click(screen.getByText('summary')); + + expect(document.body.innerHTML).toBe( + '
summarydetails
' + ); + expect(handleToggle).toHaveBeenCalledTimes(2); + }); });