Repository: capricorn86/happy-dom
Pull request #269 (2021-05-06 14:39:49)

#268@minor: Adds support for Window.parent. Adds support for dispatch…
…ing "focusin" and "focusout" event when using the methods HTMLElement.focus() and HTMLElement.blur().


Patch:
diff --git a/packages/happy-dom/README.md b/packages/happy-dom/README.md
index 18c3d226..c27834ee 100644
--- a/packages/happy-dom/README.md
+++ b/packages/happy-dom/README.md
@@ -83,12 +83,6 @@ import VM from 'vm';
 
 const window = VM.createContext(new Window());
 const document = window.document;
-const script = new VM.Script(`
-    const element = document.createElement('div');
-    const container = document.querySelector('.container');
-    element.innerHTML = 'Test';
-    container.appendChild(element);
-`);
 
 window.location.href = 'http://localhost:8080';
 
@@ -102,11 +96,15 @@ document.write(`
                   <!–– Content will be added here -->
              </div>
         </body>
+        <script>
+            const element = document.createElement('div');
+            const container = document.querySelector('.container');
+            element.innerHTML = 'Test';
+            container.appendChild(element);
+        </script>
     </html>
 `);
 
-script.runInContext(window);
-
 // Will output "Test"
 console.log(document.querySelector('.container div').innerHTML);
 ```
@@ -123,7 +121,7 @@ Returns a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe
 
 ```javascript
 window.happyDOM.whenAsyncComplete().then(() => {
-    // Do something when all async task are completed.
+    // Do something when all async tasks are completed.
 });
 ```
 
diff --git a/packages/happy-dom/src/nodes/html-element/HTMLElement.ts b/packages/happy-dom/src/nodes/html-element/HTMLElement.ts
index ac905b38..c46735f3 100644
--- a/packages/happy-dom/src/nodes/html-element/HTMLElement.ts
+++ b/packages/happy-dom/src/nodes/html-element/HTMLElement.ts
@@ -185,26 +185,30 @@ export default class HTMLElement extends Element implements IHTMLElement {
 	 * Triggers a blur event.
 	 */
 	public blur(): void {
-		const event = new Event('blur', {
-			bubbles: true,
-			composed: true
-		});
-		event.target = this;
-		event.currentTarget = this;
-		this.dispatchEvent(event);
+		for (const eventType of ['blur', 'focusout']) {
+			const event = new Event(eventType, {
+				bubbles: true,
+				composed: true
+			});
+			event.target = this;
+			event.currentTarget = this;
+			this.dispatchEvent(event);
+		}
 	}
 
 	/**
 	 * Triggers a focus event.
 	 */
 	public focus(): void {
-		const event = new Event('focus', {
-			bubbles: true,
-			composed: true
-		});
-		event.target = this;
-		event.currentTarget = this;
-		this.dispatchEvent(event);
+		for (const eventType of ['focus', 'focusin']) {
+			const event = new Event(eventType, {
+				bubbles: true,
+				composed: true
+			});
+			event.target = this;
+			event.currentTarget = this;
+			this.dispatchEvent(event);
+		}
 	}
 
 	/**
diff --git a/packages/happy-dom/src/window/IWindow.ts b/packages/happy-dom/src/window/IWindow.ts
index 5ed04226..337a0819 100644
--- a/packages/happy-dom/src/window/IWindow.ts
+++ b/packages/happy-dom/src/window/IWindow.ts
@@ -62,84 +62,85 @@ import Window from './Window';
  */
 export default interface IWindow {
 	// Public Properties
-	happyDOM: {
+	readonly happyDOM: {
 		whenAsyncComplete: () => Promise<void>;
 		cancelAsync: () => void;
 		asyncTaskManager: AsyncTaskManager;
 	};
 
 	// Global classes
-	Node: typeof Node;
-	HTMLElement: typeof HTMLElement;
-	HTMLTemplateElement: typeof HTMLTemplateElement;
-	HTMLFormElement: typeof HTMLFormElement;
-	HTMLInputElement: typeof HTMLInputElement;
-	HTMLTextAreaElement: typeof HTMLTextAreaElement;
-	HTMLImageElement: typeof HTMLImageElement;
-	HTMLScriptElement: typeof HTMLScriptElement;
-	HTMLLinkElement: typeof HTMLLinkElement;
-	HTMLStyleElement: typeof HTMLStyleElement;
-	SVGSVGElement: typeof SVGSVGElement;
-	SVGElement: typeof SVGElement;
-	Text: typeof Text;
-	Comment: typeof Comment;
-	ShadowRoot: typeof ShadowRoot;
-	Element: typeof Element;
-	DocumentFragment: typeof DocumentFragment;
-	NodeFilter: typeof NodeFilter;
-	TreeWalker: typeof TreeWalker;
-	DOMParser: typeof DOMParser;
-	MutationObserver: typeof MutationObserver;
-	Document: typeof Document;
-	HTMLDocument: typeof HTMLDocument;
-	XMLDocument: typeof XMLDocument;
-	SVGDocument: typeof SVGDocument;
-	Event: typeof Event;
-	UIEvent: typeof UIEvent;
-	CustomEvent: typeof CustomEvent;
-	AnimationEvent: typeof AnimationEvent;
-	KeyboardEvent: typeof KeyboardEvent;
-	MouseEvent: typeof MouseEvent;
-	FocusEvent: typeof FocusEvent;
-	WheelEvent: typeof WheelEvent;
-	InputEvent: typeof InputEvent;
-	ErrorEvent: typeof ErrorEvent;
-	ProgressEvent: typeof ProgressEvent;
-	EventTarget: typeof EventTarget;
-	DataTransfer: typeof DataTransfer;
-	DataTransferItem: typeof DataTransferItem;
-	DataTransferItemList: typeof DataTransferItemList;
-	URL: typeof URL;
-	Location: typeof Location;
-	CustomElementRegistry: typeof CustomElementRegistry;
-	Window: typeof Window;
-	Headers: typeof Map;
-	XMLSerializer: typeof XMLSerializer;
-	ResizeObserver: typeof ResizeObserver;
-	CSSStyleSheet: typeof CSSStyleSheet;
-	Blob: typeof Blob;
-	File: typeof File;
-	FileReader: typeof FileReader;
-	DOMException: typeof DOMException;
-	History: typeof History;
-	Screen: typeof Screen;
-	Storage: typeof Storage;
+	readonly Node: typeof Node;
+	readonly HTMLElement: typeof HTMLElement;
+	readonly HTMLTemplateElement: typeof HTMLTemplateElement;
+	readonly HTMLFormElement: typeof HTMLFormElement;
+	readonly HTMLInputElement: typeof HTMLInputElement;
+	readonly HTMLTextAreaElement: typeof HTMLTextAreaElement;
+	readonly HTMLImageElement: typeof HTMLImageElement;
+	readonly HTMLScriptElement: typeof HTMLScriptElement;
+	readonly HTMLLinkElement: typeof HTMLLinkElement;
+	readonly HTMLStyleElement: typeof HTMLStyleElement;
+	readonly SVGSVGElement: typeof SVGSVGElement;
+	readonly SVGElement: typeof SVGElement;
+	readonly Text: typeof Text;
+	readonly Comment: typeof Comment;
+	readonly ShadowRoot: typeof ShadowRoot;
+	readonly Element: typeof Element;
+	readonly DocumentFragment: typeof DocumentFragment;
+	readonly NodeFilter: typeof NodeFilter;
+	readonly TreeWalker: typeof TreeWalker;
+	readonly DOMParser: typeof DOMParser;
+	readonly MutationObserver: typeof MutationObserver;
+	readonly Document: typeof Document;
+	readonly HTMLDocument: typeof HTMLDocument;
+	readonly XMLDocument: typeof XMLDocument;
+	readonly SVGDocument: typeof SVGDocument;
+	readonly Event: typeof Event;
+	readonly UIEvent: typeof UIEvent;
+	readonly CustomEvent: typeof CustomEvent;
+	readonly AnimationEvent: typeof AnimationEvent;
+	readonly KeyboardEvent: typeof KeyboardEvent;
+	readonly MouseEvent: typeof MouseEvent;
+	readonly FocusEvent: typeof FocusEvent;
+	readonly WheelEvent: typeof WheelEvent;
+	readonly InputEvent: typeof InputEvent;
+	readonly ErrorEvent: typeof ErrorEvent;
+	readonly ProgressEvent: typeof ProgressEvent;
+	readonly EventTarget: typeof EventTarget;
+	readonly DataTransfer: typeof DataTransfer;
+	readonly DataTransferItem: typeof DataTransferItem;
+	readonly DataTransferItemList: typeof DataTransferItemList;
+	readonly URL: typeof URL;
+	readonly Location: typeof Location;
+	readonly CustomElementRegistry: typeof CustomElementRegistry;
+	readonly Window: typeof Window;
+	readonly Headers: typeof Map;
+	readonly XMLSerializer: typeof XMLSerializer;
+	readonly ResizeObserver: typeof ResizeObserver;
+	readonly CSSStyleSheet: typeof CSSStyleSheet;
+	readonly Blob: typeof Blob;
+	readonly File: typeof File;
+	readonly FileReader: typeof FileReader;
+	readonly DOMException: typeof DOMException;
+	readonly History: typeof History;
+	readonly Screen: typeof Screen;
+	readonly Storage: typeof Storage;
 
 	// Events
 	onload: (event: Event) => void;
 	onerror: (event: ErrorEvent) => void;
 
 	// Public Properties
-	document: Document;
-	customElements: CustomElementRegistry;
-	location: Location;
-	history: History;
-	navigator: { userAgent: string };
-	console: Console;
-	self: IWindow;
-	top: IWindow;
-	window: IWindow;
-	screen: Screen;
+	readonly document: Document;
+	readonly customElements: CustomElementRegistry;
+	readonly location: Location;
+	readonly history: History;
+	readonly navigator: { userAgent: string };
+	readonly console: Console;
+	readonly self: IWindow;
+	readonly top: IWindow;
+	readonly parent: IWindow;
+	readonly window: IWindow;
+	readonly screen: Screen;
 	readonly innerWidth: number;
 	readonly innerHeight: number;
 	readonly sessionStorage: Storage;
diff --git a/packages/happy-dom/src/window/Window.ts b/packages/happy-dom/src/window/Window.ts
index 212759d2..be02942b 100644
--- a/packages/happy-dom/src/window/Window.ts
+++ b/packages/happy-dom/src/window/Window.ts
@@ -68,7 +68,7 @@ const FETCH_RESPONSE_TYPE_METHODS = ['blob', 'json', 'text'];
  */
 export default class Window extends EventTarget implements IWindow, NodeJS.Global {
 	// Public Properties
-	public happyDOM = {
+	public readonly happyDOM = {
 		whenAsyncComplete: async () => {
 			return await this.happyDOM.asyncTaskManager.whenComplete();
 		},
@@ -79,77 +79,78 @@ export default class Window extends EventTarget implements IWindow, NodeJS.Globa
 	};
 
 	// Global classes
-	public Node = Node;
-	public HTMLElement = HTMLElement;
-	public HTMLTemplateElement = HTMLTemplateElement;
-	public HTMLFormElement = HTMLFormElement;
-	public HTMLInputElement = HTMLInputElement;
-	public HTMLTextAreaElement = HTMLTextAreaElement;
-	public HTMLImageElement = HTMLImageElement;
-	public HTMLScriptElement = HTMLScriptElement;
-	public HTMLLinkElement = HTMLLinkElement;
-	public HTMLStyleElement = HTMLStyleElement;
-	public SVGSVGElement = SVGSVGElement;
-	public SVGElement = SVGElement;
-	public Text = Text;
-	public Comment = Comment;
-	public ShadowRoot = ShadowRoot;
-	public Element = Element;
-	public DocumentFragment = DocumentFragment;
-	public NodeFilter = NodeFilter;
-	public TreeWalker = TreeWalker;
-	public DOMParser = DOMParser;
-	public MutationObserver = MutationObserver;
-	public Document = Document;
-	public HTMLDocument = HTMLDocument;
-	public XMLDocument = XMLDocument;
-	public SVGDocument = SVGDocument;
-	public Event = Event;
-	public UIEvent = UIEvent;
-	public CustomEvent = CustomEvent;
-	public AnimationEvent = AnimationEvent;
-	public KeyboardEvent = KeyboardEvent;
-	public MouseEvent = MouseEvent;
-	public FocusEvent = FocusEvent;
-	public WheelEvent = WheelEvent;
-	public InputEvent = InputEvent;
-	public ErrorEvent = ErrorEvent;
-	public ProgressEvent = ProgressEvent;
-	public EventTarget = EventTarget;
-	public DataTransfer = DataTransfer;
-	public DataTransferItem = DataTransferItem;
-	public DataTransferItemList = DataTransferItemList;
-	public URL = URL;
-	public Location = Location;
-	public CustomElementRegistry = CustomElementRegistry;
-	public Window = Window;
-	public Headers = Map;
-	public XMLSerializer = XMLSerializer;
-	public ResizeObserver = ResizeObserver;
-	public CSSStyleSheet = CSSStyleSheet;
-	public Blob = Blob;
-	public File = File;
-	public FileReader = FileReader;
-	public DOMException = DOMException;
-	public History = History;
-	public Screen = Screen;
-	public Storage = Storage;
+	public readonly Node = Node;
+	public readonly HTMLElement = HTMLElement;
+	public readonly HTMLTemplateElement = HTMLTemplateElement;
+	public readonly HTMLFormElement = HTMLFormElement;
+	public readonly HTMLInputElement = HTMLInputElement;
+	public readonly HTMLTextAreaElement = HTMLTextAreaElement;
+	public readonly HTMLImageElement = HTMLImageElement;
+	public readonly HTMLScriptElement = HTMLScriptElement;
+	public readonly HTMLLinkElement = HTMLLinkElement;
+	public readonly HTMLStyleElement = HTMLStyleElement;
+	public readonly SVGSVGElement = SVGSVGElement;
+	public readonly SVGElement = SVGElement;
+	public readonly Text = Text;
+	public readonly Comment = Comment;
+	public readonly ShadowRoot = ShadowRoot;
+	public readonly Element = Element;
+	public readonly DocumentFragment = DocumentFragment;
+	public readonly NodeFilter = NodeFilter;
+	public readonly TreeWalker = TreeWalker;
+	public readonly DOMParser = DOMParser;
+	public readonly MutationObserver = MutationObserver;
+	public readonly Document = Document;
+	public readonly HTMLDocument = HTMLDocument;
+	public readonly XMLDocument = XMLDocument;
+	public readonly SVGDocument = SVGDocument;
+	public readonly Event = Event;
+	public readonly UIEvent = UIEvent;
+	public readonly CustomEvent = CustomEvent;
+	public readonly AnimationEvent = AnimationEvent;
+	public readonly KeyboardEvent = KeyboardEvent;
+	public readonly MouseEvent = MouseEvent;
+	public readonly FocusEvent = FocusEvent;
+	public readonly WheelEvent = WheelEvent;
+	public readonly InputEvent = InputEvent;
+	public readonly ErrorEvent = ErrorEvent;
+	public readonly ProgressEvent = ProgressEvent;
+	public readonly EventTarget = EventTarget;
+	public readonly DataTransfer = DataTransfer;
+	public readonly DataTransferItem = DataTransferItem;
+	public readonly DataTransferItemList = DataTransferItemList;
+	public readonly URL = URL;
+	public readonly Location = Location;
+	public readonly CustomElementRegistry = CustomElementRegistry;
+	public readonly Window = Window;
+	public readonly Headers = Map;
+	public readonly XMLSerializer = XMLSerializer;
+	public readonly ResizeObserver = ResizeObserver;
+	public readonly CSSStyleSheet = CSSStyleSheet;
+	public readonly Blob = Blob;
+	public readonly File = File;
+	public readonly FileReader = FileReader;
+	public readonly DOMException = DOMException;
+	public readonly History = History;
+	public readonly Screen = Screen;
+	public readonly Storage = Storage;
 
 	// Events
 	public onload: (event: Event) => void = null;
 	public onerror: (event: ErrorEvent) => void = null;
 
 	// Public Properties
-	public document: Document;
-	public customElements: CustomElementRegistry = new CustomElementRegistry();
-	public location = new Location();
-	public history = new History();
-	public navigator = { userAgent: 'happy-dom' };
-	public console = global ? global.console : null;
-	public self = this;
-	public top = this;
-	public window = this;
-	public screen = new Screen();
+	public readonly document: Document;
+	public readonly customElements: CustomElementRegistry = new CustomElementRegistry();
+	public readonly location = new Location();
+	public readonly history = new History();
+	public readonly navigator = { userAgent: 'happy-dom' };
+	public readonly console = global ? global.console : null;
+	public readonly self = this;
+	public readonly top = this;
+	public readonly parent = this;
+	public readonly window = this;
+	public readonly screen = new Screen();
 	public readonly innerWidth = 1024;
 	public readonly innerHeight = 768;
 	public readonly sessionStorage = new Storage();


Test patch:
diff --git a/packages/happy-dom/test/nodes/html-element/HTMLElement.test.ts b/packages/happy-dom/test/nodes/html-element/HTMLElement.test.ts
index c1b5d08f..741fbfcc 100644
--- a/packages/happy-dom/test/nodes/html-element/HTMLElement.test.ts
+++ b/packages/happy-dom/test/nodes/html-element/HTMLElement.test.ts
@@ -240,14 +240,22 @@ describe('HTMLElement', () => {
 		});
 	}
 
-	for (const eventType of ['click', 'blur', 'focus']) {
+	for (const eventType of ['click', 'blur', 'focus', 'focusin', 'focusout']) {
 		describe(`${eventType}()`, () => {
 			test(`Dispatches a "${eventType}" event.`, () => {
+				let methodName = eventType;
 				let triggeredEvent = null;
+
+				if (eventType === 'focusin') {
+					methodName = 'focus';
+				} else if (eventType === 'focusout') {
+					methodName = 'blur';
+				}
+
 				element.addEventListener(eventType, event => {
 					triggeredEvent = event;
 				});
-				element[eventType]();
+				element[methodName]();
 				expect(triggeredEvent).toEqual({
 					_immediatePropagationStopped: false,
 					_propagationStopped: false,