From e69a953ac48b31fa9d7b45e8fba47086ac80d994 Mon Sep 17 00:00:00 2001 From: markgaze Date: Fri, 24 Jun 2022 16:35:46 +0100 Subject: [PATCH] #517@minor: Implement dialog element. --- packages/happy-dom/src/config/ElementTag.ts | 3 +- packages/happy-dom/src/index.ts | 6 +- .../html-dialog-element/HTMLDialogElement.ts | 52 +++++++++ .../html-dialog-element/IHTMLDialogElement.ts | 15 +++ packages/happy-dom/src/window/IWindow.ts | 2 + packages/happy-dom/src/window/Window.ts | 2 + .../HTMLDialogElement.test.ts | 110 ++++++++++++++++++ 7 files changed, 188 insertions(+), 2 deletions(-) create mode 100644 packages/happy-dom/src/nodes/html-dialog-element/HTMLDialogElement.ts create mode 100644 packages/happy-dom/src/nodes/html-dialog-element/IHTMLDialogElement.ts create mode 100644 packages/happy-dom/test/nodes/html-dialog-element/HTMLDialogElement.test.ts diff --git a/packages/happy-dom/src/config/ElementTag.ts b/packages/happy-dom/src/config/ElementTag.ts index 4bb6729a3..92ba25b92 100644 --- a/packages/happy-dom/src/config/ElementTag.ts +++ b/packages/happy-dom/src/config/ElementTag.ts @@ -16,6 +16,7 @@ import HTMLBaseElement from '../nodes/html-base-element/HTMLBaseElement'; import HTMLSelectElement from '../nodes/html-select-element/HTMLSelectElement'; import HTMLOptionElement from '../nodes/html-option-element/HTMLOptionElement'; import HTMLOptGroupElement from '../nodes/html-opt-group-element/HTMLOptGroupElement'; +import HTMLDialogElement from '../nodes/html-dialog-element/HTMLDialogElement'; export default { A: HTMLElement, @@ -67,7 +68,7 @@ export default { DEL: HTMLElement, DETAILS: HTMLElement, DFN: HTMLElement, - DIALOG: HTMLElement, + DIALOG: HTMLDialogElement, DIV: HTMLElement, DL: HTMLElement, DT: HTMLElement, diff --git a/packages/happy-dom/src/index.ts b/packages/happy-dom/src/index.ts index a1c97ae2d..3966c5bb9 100644 --- a/packages/happy-dom/src/index.ts +++ b/packages/happy-dom/src/index.ts @@ -104,6 +104,8 @@ import Storage from './storage/Storage'; import DOMRect from './nodes/element/DOMRect'; import { URLSearchParams } from 'url'; import Selection from './selection/Selection'; +import HTMLDialogElement from './nodes/html-dialog-element/HTMLDialogElement'; +import IHTMLDialogElement from './nodes/html-dialog-element/IHTMLDialogElement'; export { GlobalWindow, @@ -211,5 +213,7 @@ export { Storage, DOMRect, URLSearchParams, - Selection + Selection, + HTMLDialogElement, + IHTMLDialogElement }; diff --git a/packages/happy-dom/src/nodes/html-dialog-element/HTMLDialogElement.ts b/packages/happy-dom/src/nodes/html-dialog-element/HTMLDialogElement.ts new file mode 100644 index 000000000..0d69e9d94 --- /dev/null +++ b/packages/happy-dom/src/nodes/html-dialog-element/HTMLDialogElement.ts @@ -0,0 +1,52 @@ +import Event from '../../event/Event'; +import HTMLElement from '../html-element/HTMLElement'; +import IHTMLDialogElement from './IHTMLDialogElement'; + +/** + * HTML Dialog Element. + * + * Reference: + * https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement. + */ +export default class HTMLDialogElement extends HTMLElement implements IHTMLDialogElement { + /** + * Returns returnValue. + * + * @returns ReturnValue. + */ + public returnValue: string; + + /** + * Returns open. + * + * @returns Open. + */ + public get open(): boolean { + return this.hasAttributeNS(null, 'open'); + } + + /** + * Closes the dialog. + * + * @param returnValue ReturnValue. + */ + public close(returnValue?: string): void { + this.removeAttributeNS(null, 'open'); + this.returnValue = returnValue; + this.dispatchEvent(new Event('close', { bubbles: false, cancelable: false })); + } + + /** + * Shows the modal. + */ + public showModal(): void { + this.setAttributeNS(null, 'open', ''); + } + + /** + * Shows the dialog. + */ + public show(): void { + this.setAttributeNS(null, 'open', ''); + } +} diff --git a/packages/happy-dom/src/nodes/html-dialog-element/IHTMLDialogElement.ts b/packages/happy-dom/src/nodes/html-dialog-element/IHTMLDialogElement.ts new file mode 100644 index 000000000..4fbf8c455 --- /dev/null +++ b/packages/happy-dom/src/nodes/html-dialog-element/IHTMLDialogElement.ts @@ -0,0 +1,15 @@ +import IHTMLElement from '../html-element/IHTMLElement'; + +/** + * HTML Dialog Element. + * + * Reference: + * https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement. + */ +export default interface IHTMLDialogElement extends IHTMLElement { + open: boolean; + returnValue: string; + close(returnValue?: string): void; + showModal(): void; + show(): void; +} diff --git a/packages/happy-dom/src/window/IWindow.ts b/packages/happy-dom/src/window/IWindow.ts index 38e4bf2d8..af20db73f 100644 --- a/packages/happy-dom/src/window/IWindow.ts +++ b/packages/happy-dom/src/window/IWindow.ts @@ -24,6 +24,7 @@ import HTMLBaseElement from '../nodes/html-base-element/HTMLBaseElement'; import SVGSVGElement from '../nodes/svg-element/SVGSVGElement'; import SVGElement from '../nodes/svg-element/SVGElement'; import HTMLScriptElement from '../nodes/html-script-element/HTMLScriptElement'; +import HTMLDialogElement from '../nodes/html-dialog-element/HTMLDialogElement'; import HTMLImageElement from '../nodes/html-image-element/HTMLImageElement'; import Image from '../nodes/html-image-element/Image'; import DocumentFragment from '../nodes/document-fragment/DocumentFragment'; @@ -112,6 +113,7 @@ export default interface IWindow extends IEventTarget, NodeJS.Global { readonly HTMLLabelElement: typeof HTMLLabelElement; readonly HTMLMetaElement: typeof HTMLMetaElement; readonly HTMLBaseElement: typeof HTMLBaseElement; + readonly HTMLDialogElement: typeof HTMLDialogElement; readonly SVGSVGElement: typeof SVGSVGElement; readonly SVGElement: typeof SVGElement; readonly Image: typeof Image; diff --git a/packages/happy-dom/src/window/Window.ts b/packages/happy-dom/src/window/Window.ts index a2d63cd4a..9da160df8 100644 --- a/packages/happy-dom/src/window/Window.ts +++ b/packages/happy-dom/src/window/Window.ts @@ -21,6 +21,7 @@ import HTMLSlotElement from '../nodes/html-slot-element/HTMLSlotElement'; import HTMLLabelElement from '../nodes/html-label-element/HTMLLabelElement'; import HTMLMetaElement from '../nodes/html-meta-element/HTMLMetaElement'; import HTMLBaseElement from '../nodes/html-base-element/HTMLBaseElement'; +import HTMLDialogElement from '../nodes/html-dialog-element/HTMLDialogElement'; import SVGSVGElement from '../nodes/svg-element/SVGSVGElement'; import SVGElement from '../nodes/svg-element/SVGElement'; import HTMLScriptElement from '../nodes/html-script-element/HTMLScriptElement'; @@ -129,6 +130,7 @@ export default class Window extends EventTarget implements IWindow { public readonly HTMLSlotElement = HTMLSlotElement; public readonly HTMLMetaElement = HTMLMetaElement; public readonly HTMLBaseElement = HTMLBaseElement; + public readonly HTMLDialogElement = HTMLDialogElement; public readonly SVGSVGElement = SVGSVGElement; public readonly SVGElement = SVGElement; public readonly Text = Text; diff --git a/packages/happy-dom/test/nodes/html-dialog-element/HTMLDialogElement.test.ts b/packages/happy-dom/test/nodes/html-dialog-element/HTMLDialogElement.test.ts new file mode 100644 index 000000000..583008b68 --- /dev/null +++ b/packages/happy-dom/test/nodes/html-dialog-element/HTMLDialogElement.test.ts @@ -0,0 +1,110 @@ +import { Event } from 'src'; +import Document from '../../../src/nodes/document/Document'; +import HTMLDialogElement from '../../../src/nodes/html-dialog-element/HTMLDialogElement'; +import Window from '../../../src/window/Window'; + +describe('HTMLDialogElement', () => { + let window: Window; + let document: Document; + let element: HTMLDialogElement; + + beforeEach(() => { + window = new Window(); + document = window.document; + element = document.createElement('dialog'); + }); + + describe('open', () => { + it('Should be closed by default', () => { + expect(element.open).toBe(false); + }); + + it('Should be open when show has been called', () => { + element.show(); + expect(element.open).toBe(true); + }); + + it('Should be open when showModal has been called', () => { + element.showModal(); + expect(element.open).toBe(true); + }); + }); + + describe('returnValue', () => { + it('Should be undefined by default', () => { + expect(element.returnValue).toBe(undefined); + }); + + it('Should be set when close has been called with a return value', () => { + element.close('foo'); + expect(element.returnValue).toBe('foo'); + }); + + it('Should be possible to set manually', () => { + element.returnValue = 'foo'; + expect(element.returnValue).toBe('foo'); + }); + }); + + describe('close', () => { + it('Should be possible to close an open dialog', () => { + element.show(); + element.close(); + expect(element.open).toBe(false); + expect(element.getAttributeNS(null, 'open')).toBe(null); + }); + + it('Should be possible to close an open modal dialog', () => { + element.showModal(); + element.close(); + expect(element.open).toBe(false); + expect(element.getAttributeNS(null, 'open')).toBe(null); + }); + + it('Should be possible to close the dialog with a return value', () => { + element.show(); + element.close('foo'); + expect(element.returnValue).toBe('foo'); + }); + + it('Should be possible to close the modal dialog with a return value', () => { + element.showModal(); + element.close('foo'); + expect(element.returnValue).toBe('foo'); + }); + + it('Should dispatch a close event', () => { + let dispatched: Event = null; + element.addEventListener('close', (event: Event) => (dispatched = event)); + element.show(); + element.close(); + expect(dispatched.cancelable).toBe(false); + expect(dispatched.bubbles).toBe(false); + }); + + it('Should dispatch a close event when closing a modal', () => { + let dispatched: Event = null; + element.addEventListener('close', (event: Event) => (dispatched = event)); + element.showModal(); + element.close(); + expect(dispatched.cancelable).toBe(false); + expect(dispatched.bubbles).toBe(false); + }); + }); + + describe('showModal', () => { + it('Should be possible to show a modal dialog', () => { + element.showModal(); + expect(element.open).toBe(true); + expect(element.getAttributeNS(null, 'open')).toBe(''); + }); + }); + + describe('show', () => { + it('Should be possible to show a dialog', () => { + element.show(); + expect(element.open).toBe(true); + expect(element.getAttributeNS(null, 'open')).toBe(''); + }); + }); +});