Repository: capricorn86/happy-dom
Pull request #354 (2022-02-16 13:10:34)

#306@minor: Adds support for window.matchMedia and MediaQueryList.


Patch:
diff --git a/README.md b/README.md
index 5732fe88..d3eabcc0 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
 
 # About
 
-A [JSDOM](https://github.com/jsdom/jsdom) alternative with support for server side rendering of web components.
+A [JSDOM](https://github.com/jsdom/jsdom) alternative with focus on performance.
 
 Happy DOM aim to support the most common functionality of a web browser.
 
diff --git a/packages/global-registrator/README.md b/packages/global-registrator/README.md
index 6a189ea8..bd53710a 100644
--- a/packages/global-registrator/README.md
+++ b/packages/global-registrator/README.md
@@ -3,7 +3,7 @@
 
 # About
 
-A [JSDOM](https://github.com/jsdom/jsdom) alternative with support for server side rendering of web components.
+A [JSDOM](https://github.com/jsdom/jsdom) alternative with focus on performance.
 
 [Happy DOM](https://github.com/capricorn86/happy-dom) aim to support the most common functionality of a web browser.
 
diff --git a/packages/happy-dom/README.md b/packages/happy-dom/README.md
index c27834ee..aa22d5f1 100644
--- a/packages/happy-dom/README.md
+++ b/packages/happy-dom/README.md
@@ -3,7 +3,7 @@
 
 # About
 
-A [JSDOM](https://github.com/jsdom/jsdom) alternative with support for server side rendering of web components.
+A [JSDOM](https://github.com/jsdom/jsdom) alternative with focus on performance.
 
 [Happy DOM](https://github.com/capricorn86/happy-dom) aim to support the most common functionality of a web browser.
 
diff --git a/packages/happy-dom/package.json b/packages/happy-dom/package.json
index 02a06d36..8e6c2771 100644
--- a/packages/happy-dom/package.json
+++ b/packages/happy-dom/package.json
@@ -5,7 +5,7 @@
 	"homepage": "https://github.com/capricorn86/happy-dom",
 	"repository": "https://github.com/capricorn86/happy-dom",
 	"author": "David Ortner",
-	"description": "A jsdom alternative with support for server side rendering of web components.",
+	"description": "A jsdom alternative with focus on performance.",
 	"main": "lib/index.js",
 	"keywords": [
 		"jsdom",
diff --git a/packages/happy-dom/src/event/EventTarget.ts b/packages/happy-dom/src/event/EventTarget.ts
index 7d241a14..9b455b02 100644
--- a/packages/happy-dom/src/event/EventTarget.ts
+++ b/packages/happy-dom/src/event/EventTarget.ts
@@ -52,6 +52,12 @@ export default abstract class EventTarget implements IEventTarget {
 
 		event.currentTarget = this;
 
+		const onEventName = 'on' + event.type.toLowerCase();
+
+		if (typeof this[onEventName] === 'function') {
+			this[onEventName].call(this, event);
+		}
+
 		if (this._listeners[event.type]) {
 			for (const listener of this._listeners[event.type]) {
 				if ((<IEventListener>listener).handleEvent) {
diff --git a/packages/happy-dom/src/match-media/MediaQueryList.ts b/packages/happy-dom/src/match-media/MediaQueryList.ts
new file mode 100644
index 00000000..250aa347
--- /dev/null
+++ b/packages/happy-dom/src/match-media/MediaQueryList.ts
@@ -0,0 +1,52 @@
+import EventTarget from '../event/EventTarget';
+import Event from '../event/Event';
+
+/**
+ * Media Query List.
+ *
+ * Reference:
+ * https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList.
+ */
+export default class MediaQueryList extends EventTarget {
+	public _matches = false;
+	public _media = '';
+	public onchange: (event: Event) => void = null;
+
+	/**
+	 * Returns "true" if the document matches.
+	 *
+	 * @returns Matches.
+	 */
+	public get matches(): boolean {
+		return this._matches;
+	}
+
+	/**
+	 * Returns the serialized media query.
+	 *
+	 * @returns Serialized media query.
+	 */
+	public get media(): string {
+		return this._media;
+	}
+
+	/**
+	 * Adds a listener.
+	 *
+	 * @deprecated
+	 * @param callback Callback.
+	 */
+	public addListener(callback: (event: Event) => void): void {
+		this.addEventListener('change', callback);
+	}
+
+	/**
+	 * Removes listener.
+	 *
+	 * @deprecated
+	 * @param callback Callback.
+	 */
+	public removeListener(callback: (event: Event) => void): void {
+		this.removeEventListener('change', callback);
+	}
+}
diff --git a/packages/happy-dom/src/nodes/node/Node.ts b/packages/happy-dom/src/nodes/node/Node.ts
index 491d5a6f..ec7709e7 100644
--- a/packages/happy-dom/src/nodes/node/Node.ts
+++ b/packages/happy-dom/src/nodes/node/Node.ts
@@ -387,12 +387,6 @@ export default class Node extends EventTarget implements INode {
 	 * @override
 	 */
 	public dispatchEvent(event: Event): boolean {
-		const onEventName = 'on' + event.type.toLowerCase();
-
-		if (typeof this[onEventName] === 'function') {
-			this[onEventName].call(this, event);
-		}
-
 		const returnValue = super.dispatchEvent(event);
 
 		if (event.bubbles && !event._propagationStopped) {
diff --git a/packages/happy-dom/src/window/Window.ts b/packages/happy-dom/src/window/Window.ts
index 4357ba8e..3c58909b 100644
--- a/packages/happy-dom/src/window/Window.ts
+++ b/packages/happy-dom/src/window/Window.ts
@@ -66,6 +66,7 @@ import IWindow from './IWindow';
 import URLSearchParams from '../url-search-params/URLSearchParams';
 import HTMLCollection from '../nodes/element/HTMLCollection';
 import NodeList from '../nodes/node/NodeList';
+import MediaQueryList from '../match-media/MediaQueryList';
 
 const FETCH_RESPONSE_TYPE_METHODS = ['blob', 'json', 'text'];
 
@@ -165,6 +166,7 @@ export default class Window extends EventTarget implements IWindow, NodeJS.Globa
 	public readonly screen = new Screen();
 	public readonly innerWidth = 1024;
 	public readonly innerHeight = 768;
+	public readonly devicePixelRatio = 1;
 	public readonly sessionStorage = new Storage();
 	public readonly localStorage = new Storage();
 
@@ -346,6 +348,18 @@ export default class Window extends EventTarget implements IWindow, NodeJS.Globa
 		this.scroll(x, y);
 	}
 
+	/**
+	 * Returns a new MediaQueryList object that can then be used to determine if the document matches the media query string.
+	 *
+	 * @param mediaQueryString A string specifying the media query to parse into a MediaQueryList.
+	 * @returns A new MediaQueryList.
+	 */
+	public matchMedia(mediaQueryString: string): MediaQueryList {
+		const mediaQueryList = new MediaQueryList();
+		mediaQueryList._media = mediaQueryString;
+		return mediaQueryList;
+	}
+
 	/**
 	 * Sets a timer which executes a function once the timer expires.
 	 *
diff --git a/packages/jest-environment/README.md b/packages/jest-environment/README.md
index ff520926..e8218f47 100644
--- a/packages/jest-environment/README.md
+++ b/packages/jest-environment/README.md
@@ -3,7 +3,7 @@
 
 # About
 
-A [JSDOM](https://github.com/jsdom/jsdom) alternative with support for server side rendering of web components.
+A [JSDOM](https://github.com/jsdom/jsdom) alternative with focus on performance.
 
 [Happy DOM](https://github.com/capricorn86/happy-dom) aim to support the most common functionality of a web browser.
 
diff --git a/packages/server-rendering/README.md b/packages/server-rendering/README.md
index 7eec22ba..71993f98 100644
--- a/packages/server-rendering/README.md
+++ b/packages/server-rendering/README.md
@@ -3,7 +3,7 @@
 
 # About
 
-A [JSDOM](https://github.com/jsdom/jsdom) alternative with support for server side rendering of web components.
+A [JSDOM](https://github.com/jsdom/jsdom) alternative with focus on performance.
 
 [Happy DOM](https://github.com/capricorn86/happy-dom) aim to support the most common functionality of a web browser.
 


Test patch:
diff --git a/packages/happy-dom/test/event/EventTarget.test.ts b/packages/happy-dom/test/event/EventTarget.test.ts
index 210b0395..a56d896c 100644
--- a/packages/happy-dom/test/event/EventTarget.test.ts
+++ b/packages/happy-dom/test/event/EventTarget.test.ts
@@ -73,4 +73,19 @@ describe('EventTarget', () => {
 			expect(recievedEvent).toBe(null);
 		});
 	});
+
+	describe('dispatchEvent()', () => {
+		it('Triggers callback properties with "on" as prefix.', () => {
+			let recievedEvent: Event = null;
+			const listener = (event: Event): void => {
+				recievedEvent = event;
+			};
+			const dispatchedEvent = new Event(EVENT_TYPE);
+			eventTarget[`on${EVENT_TYPE}`] = listener;
+			eventTarget.dispatchEvent(dispatchedEvent);
+			expect(recievedEvent).toBe(dispatchedEvent);
+			expect(recievedEvent.target).toBe(eventTarget);
+			expect(recievedEvent.currentTarget).toBe(eventTarget);
+		});
+	});
 });
diff --git a/packages/happy-dom/test/window/Window.test.ts b/packages/happy-dom/test/window/Window.test.ts
index 2b9cb142..dba721f6 100644
--- a/packages/happy-dom/test/window/Window.test.ts
+++ b/packages/happy-dom/test/window/Window.test.ts
@@ -113,6 +113,18 @@ describe('Window', () => {
 		});
 	});
 
+	describe('matchMedia()', () => {
+		it('Returns a new MediaQueryList object that can then be used to determine if the document matches the media query string.', () => {
+			const mediaQueryString = '(max-width: 600px)';
+			const mediaQueryList = window.matchMedia(mediaQueryString);
+			expect(mediaQueryList.matches).toBe(false);
+			expect(mediaQueryList.media).toBe(mediaQueryString);
+			expect(mediaQueryList.onchange).toBe(null);
+			expect(typeof mediaQueryList.addEventListener).toBe('function');
+			expect(typeof mediaQueryList.removeEventListener).toBe('function');
+		});
+	});
+
 	describe('fetch()', () => {
 		it('Handles successful JSON request.', async () => {
 			const expectedUrl = 'https://localhost:8080/path/';