Repository: capricorn86/happy-dom
Pull request #390 (2022-03-04 15:18:44)

#388@minor: Improves support for window.navigator.


Patch:
diff --git a/packages/happy-dom/src/navigator/MimeType.ts b/packages/happy-dom/src/navigator/MimeType.ts
index c558835e..2e185848 100644
--- a/packages/happy-dom/src/navigator/MimeType.ts
+++ b/packages/happy-dom/src/navigator/MimeType.ts
@@ -4,55 +4,29 @@ import Plugin from './Plugin';
  * MimeType.
  */
 export default class MimeType {
-	private readonly _description: string;
-	private readonly _enabledPlugin: Plugin;
-	private readonly _suffixes: string;
-	private readonly _type: string;
+	public readonly description: string;
+	public readonly enabledPlugin: Plugin;
+	public readonly suffixes: string;
+	public readonly type: string;
+
 	/**
+	 * Constructor.
+	 *
 	 * @param description
 	 * @param enabledPlugin
 	 * @param suffixes
 	 * @param type
 	 */
 	constructor(description: string, enabledPlugin: Plugin, suffixes: string, type: string) {
-		this._description = description;
-		this._enabledPlugin = enabledPlugin;
-		this._suffixes = suffixes;
-		this._type = type;
+		this.description = description;
+		this.enabledPlugin = enabledPlugin;
+		this.suffixes = suffixes;
+		this.type = type;
 	}
 
 	/**
-	 * Description.
-	 *
-	 * @returns String.
-	 */
-	public get description(): string {
-		return this._description ? this._description : '';
-	}
-	/**
-	 * EnabledPlugin.
-	 *
-	 * @returns String.
-	 */
-	public get enabledPlugin(): Plugin {
-		return this._enabledPlugin ? this._enabledPlugin : null;
-	}
-	/**
-	 * Suffixes.
+	 * Returns the object as a string.
 	 *
-	 * @returns Suffixes.
-	 */
-	public get suffixes(): string {
-		return this._suffixes ? this._suffixes : '';
-	}
-
-	/**
-	 * @returns String.
-	 */
-	public get type(): string {
-		return this._type ? this._type : '';
-	}
-	/**
 	 * @returns String.
 	 */
 	public toString(): string {
diff --git a/packages/happy-dom/src/navigator/MimeTypeArray.ts b/packages/happy-dom/src/navigator/MimeTypeArray.ts
index cb750d2d..4195a6d7 100644
--- a/packages/happy-dom/src/navigator/MimeTypeArray.ts
+++ b/packages/happy-dom/src/navigator/MimeTypeArray.ts
@@ -2,37 +2,44 @@ import MimeType from './MimeType';
 
 /**
  * MimeTypeArray.
+ *
+ * Reference:
+ * https://developer.mozilla.org/en-US/docs/Web/API/MimeTypeArray.
  */
 export default class MimeTypeArray {
 	[n: number]: MimeType;
-	public readonly _length: number;
+	public readonly length: number;
 
 	/**
 	 * Constructor.
 	 *
+	 * @param mimeTypes
 	 */
-	constructor() {}
+	constructor(mimeTypes: MimeType[]) {
+		for (let i = 0, max = mimeTypes.length; i < max; i++) {
+			this[i] = mimeTypes[i];
+			this[mimeTypes[i].type] = mimeTypes[i];
+		}
+		this.length = mimeTypes.length;
+	}
 
 	/**
 	 * @param index
 	 */
 	public item(index: number): MimeType {
-		return this[index];
+		return this[index] || null;
 	}
+
 	/**
 	 * @param name
 	 */
 	public namedItem(name: string): MimeType {
-		return this[name];
-	}
-	/**
-	 * @returns MimeType length.
-	 */
-	public get length(): number {
-		return this._length ? this._length : 0;
+		return this[name] || null;
 	}
 
 	/**
+	 * Returns the object as a string.
+	 *
 	 * @returns String.
 	 */
 	public toString(): string {
diff --git a/packages/happy-dom/src/navigator/Navigator.ts b/packages/happy-dom/src/navigator/Navigator.ts
index 030ac297..540a0ef2 100644
--- a/packages/happy-dom/src/navigator/Navigator.ts
+++ b/packages/happy-dom/src/navigator/Navigator.ts
@@ -2,207 +2,89 @@ import MimeTypeArray from './MimeTypeArray';
 import PluginArray from './PluginArray';
 
 /**
- * Navigator API.
- * Based on FireFox.
+ * Browser Navigator API.
+ *
+ * Mocked information is taken from FireFox.
  *
  * Reference:
  * https://html.spec.whatwg.org/multipage/system-state.html#dom-navigator.
  */
 export default class Navigator {
-	/**
-	 * Constructor.
-	 **/
-	constructor() {}
-	/**
-	 * Readonly cookieEnabled.
-	 *
-	 * @returns CookieEnabled: False if setting a cookie will be ignored and true otherwise.
-	 */
-	public get cookieEnabled(): boolean {
-		return true;
-	}
-	/**
-	 * Readonly credentials.
-	 *
-	 * TODO: NOT IMPL.
-	 */
-	public get credentials(): null {
-		return null;
-	}
-	/**
-	 * Readonly geolocation.
-	 *
-	 * TODO: NOT IMPL.
-	 **/
-	public get geolocation(): null {
-		return null;
-	}
-	/**
-	 * Readonly language.
-	 *
-	 * @returns Language: String representing the preferred language of the user, usually the language of the browser UI.
-	 **/
-	public get language(): string {
-		return 'en-US';
-	}
-	/**
-	 * Readonly language.
-	 *
-	 * @returns Languages: Array of string representing the user's preferred languages.
-	 **/
-	public get languages(): string[] {
-		return ['en-US'];
-	}
-	/**
-	 * Readonly Locks.
-	 *
-	 * TODO: NOT IMPL.
-	 * */
-	public get locks(): null {
-		return null;
-	}
-	/**
-	 * Readonly maxTouchPoints.
-	 *
-	 * @returns MaxTouchPoints: the maximum number of simultaneous touch contact points are supported by the current device.
-	 * */
-	public get maxTouchPoints(): number {
-		return 0;
-	}
-	/**
-	 * Readonly hardwareConcurrency.
-	 *
-	 * @returns HardwareConcurrency: Number of logical processors available to run threads on the user's computer.
-	 **/
-	public get hardwareConcurrency(): number {
-		return 8;
-	}
-	/**
-	 * Readonly appCodeName.
-	 *
-	 * @returns AppCodeName: 'Mozilla'.
-	 */
-	public get appCodeName(): string {
-		return 'Mozilla';
-	}
-	/**
-	 * Readonly appName.
-	 *
-	 * @returns AppName: 'Netscape'.
-	 * */
-	public get appName(): string {
-		return 'Netscape';
-	}
-	/**
-	 * Readonly appVersion.
-	 *
-	 * @returns AppVersion: happy-dom self defined.
-	 **/
-	public get appVersion(): string {
-		return '5.0 (Windows)';
-	}
+	// False if setting a cookie will be ignored and true otherwise.
+	public readonly cookieEnabled: boolean = true;
 
-	/**
-	 * Readonly platform.
-	 * "MacIntel", "Win32", "FreeBSD i386", "WebTV OS".
-	 *
-	 * @returns Platform: "Win32".
-	 */
-	public get platform(): string {
-		return 'Win32';
-	}
-	/**
-	 * Readonly product.
-	 *
-	 * @returns Product: "Gecko".
-	 */
-	public get product(): string {
-		return 'Gecko';
-	}
-	/**
-	 * Readonly productSub.
-	 *
-	 * @returns ProductSub: "20100101".
-	 */
-	public get productSub(): string {
-		return '20100101';
-	}
-	/**
-	 * Readonly vendor.
-	 *
-	 * @returns Vendor: "".
-	 */
-	public get vendor(): string {
-		return '';
-	}
-	/**
-	 * Readonly vendorSub.
-	 *
-	 * @returns VendorSub: "".
-	 */
-	public get vendorSub(): string {
-		return '';
-	}
-	/**
-	 * Readonly userAgent.
-	 * "appCodeName/appVersion number (Platform; Security; OS-or-CPU; Localization; rv: revision-version-number) product/productSub Application-Name Application-Name-version".
-	 *
-	 * @returns UserAgent: happy-dom self defined.
-	 */
-	public get userAgent(): string {
-		return 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:97.0) Gecko/20100101 Firefox/97.0';
-	}
-	/**
-	 * Readonly onLine.
-	 *
-	 * @returns Boolean value indicating whether the browser is working online.
-	 **/
-	public get onLine(): boolean {
-		return true;
-	}
-	/**
-	 * Readonly permissions.
-	 *
-	 * TODO: NOT IMPL.
-	 **/
-	public get permissions(): null {
-		return null;
-	}
-	/**
-	 * Readonly webdriver.
-	 *
-	 * @returns Boolean Indicates whether the user agent is controlled by automation.
-	 * */
-	public get webdriver(): boolean {
-		return false;
-	}
-	/**
-	 * Readonly doNotTrack.
-	 * The value of the property reflects that of the DNT HTTP header, i.e. Values of "1", "0", or "unspecified".
-	 *
-	 * @returns The user's Do Not Track setting, which indicates whether the user is requesting web sites and advertisers to not track them.
-	 **/
-	public get doNotTrack(): string {
-		return 'unspecified';
-	}
-	/**
-	 * Readonly mimeTypes.
-	 *
-	 * TODO: NOT ALL IMPL.
-	 **/
-	public get mimeTypes(): MimeTypeArray {
-		return new MimeTypeArray();
-	}
-	/**
-	 * Readonly plugins.
-	 *
-	 * TODO: NOT ALL IMPL.
-	 **/
-	public get plugins(): PluginArray {
-		return new PluginArray();
-	}
+	// TODO: Not implemented.
+	public readonly credentials: string = null;
+
+	// TODO: Not implemented.
+	public readonly geolocation: string = null;
+
+	// String representing the preferred language of the user, usually the language of the browser UI.
+	public readonly language: string = 'en-US';
+
+	// Array of string representing the user's preferred languages.
+	public readonly languages: string[] = ['en-US', 'en'];
+
+	// TODO: Not implemented.
+	public readonly locks: string = null;
+
+	// Maximum number of simultaneous touch contact points are supported by the current device.
+	public readonly maxTouchPoints: number = 0;
+
+	// Number of logical processors available to run threads on the user's computer.
+	public readonly hardwareConcurrency: number = 8;
+
+	// Browser app code name.
+	public readonly appCodeName: string = 'Mozilla';
+
+	// Browser app name.
+	public readonly appName: string = 'Netscape';
+
+	// Browser app version.
+	public readonly appVersion: string = '5.0 (Windows)';
+
+	// Browser platform.
+	public readonly platform: string = 'Win32';
+
+	// Browser product.
+	public readonly product: string = 'Gecko';
+
+	// Browser product sub.
+	public readonly productSub: string = '20100101';
+
+	// Browser vendor.
+	public readonly vendor: string = '';
+
+	// Browser vendor sub.
+	public readonly vendorSub: string = '';
+
+	// Browser user agent.
+	// "appCodeName/appVersion number (Platform; Security; OS-or-CPU; Localization; rv: revision-version-number) product/productSub Application-Name Application-Name-version".
+	public readonly userAgent: string =
+		'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:97.0) Gecko/20100101 Firefox/97.0';
+
+	// Boolean value indicating whether the browser is working online.
+	public readonly onLine: boolean = true;
+
+	// TODO: Not implemented.
+	public readonly permissions: string = null;
+
+	// Boolean Indicates whether the user agent is controlled by automation.
+	public readonly webdriver: boolean = true;
+
+	// The user's Do Not Track setting, which indicates whether the user is requesting web sites and advertisers to not track them.
+	// The value of the property reflects that of the DNT HTTP header, i.e. Values of "1", "0", or "unspecified".
+	public readonly doNotTrack: string = 'unspecified';
+
+	// Browser mime-types.
+	public readonly mimeTypes: MimeTypeArray = new MimeTypeArray([]);
+
+	// Browser plugins.
+	public readonly plugins: PluginArray = new PluginArray([]);
 
 	/**
+	 * Returns the object as a string.
+	 *
 	 * @returns String.
 	 */
 	public toString(): string {
diff --git a/packages/happy-dom/src/navigator/Plugin.ts b/packages/happy-dom/src/navigator/Plugin.ts
index 26822a03..317255c5 100644
--- a/packages/happy-dom/src/navigator/Plugin.ts
+++ b/packages/happy-dom/src/navigator/Plugin.ts
@@ -5,15 +5,30 @@ import MimeType from './MimeType';
  */
 export default class Plugin {
 	[n: number]: MimeType;
-	public readonly _description: string;
-	public readonly _filename: string;
-	public readonly _name: string;
-	public readonly _length: number;
+	public readonly length: number = 0;
+	public readonly description: string;
+	public readonly filename: string;
+	public readonly name: string;
 
 	/**
 	 * Constructor.
-	 */
-	constructor() {}
+	 *
+	 * @param mimeTypes Mime types.
+	 * @param description Description.
+	 * @param filename Filename.
+	 * @param name Name.
+	 */
+	constructor(mimeTypes: MimeType[], description: string, filename: string, name: string) {
+		this.description = description;
+		this.filename = filename;
+		this.name = name;
+
+		for (let i = 0, max = mimeTypes.length; i < max; i++) {
+			this[i] = mimeTypes[i];
+			this[mimeTypes[i].type] = mimeTypes[i];
+		}
+		this.length = mimeTypes.length;
+	}
 
 	/**
 	 * Item.
@@ -22,7 +37,7 @@ export default class Plugin {
 	 * @returns IMimeType.
 	 */
 	public item(index: number): MimeType {
-		return this[index];
+		return this[index] || null;
 	}
 
 	/**
@@ -32,34 +47,11 @@ export default class Plugin {
 	 * @returns IMimeType.
 	 */
 	public namedItem(name: string): MimeType {
-		return this[name];
+		return this[name] || null;
 	}
 
 	/**
-	 *
-	 */
-	public get description(): string {
-		return this._description ? this._description : '';
-	}
-	/**
-	 *
-	 */
-	public get filename(): string {
-		return this._filename ? this._filename : '';
-	}
-	/**
-	 *
-	 */
-	public get length(): number {
-		return this._length ? this._length : 0;
-	}
-	/**
-	 *
-	 */
-	public get name(): string {
-		return this._name ? this._name : '';
-	}
-	/**
+	 * Returns the object as a string.
 	 *
 	 * @returns String.
 	 */
diff --git a/packages/happy-dom/src/navigator/PluginArray.ts b/packages/happy-dom/src/navigator/PluginArray.ts
index 2fc91f3d..c907c9eb 100644
--- a/packages/happy-dom/src/navigator/PluginArray.ts
+++ b/packages/happy-dom/src/navigator/PluginArray.ts
@@ -5,45 +5,51 @@ import Plugin from './Plugin';
  */
 export default class PluginArray {
 	[n: number]: Plugin;
-	public readonly _length: number;
+	public readonly length: number;
 
 	/**
 	 * Constructor.
+	 *
+	 * @param plugins Plugins.
 	 */
-	constructor() {}
+	constructor(plugins: Plugin[]) {
+		for (let i = 0, max = plugins.length; i < max; i++) {
+			this[i] = plugins[i];
+			this[plugins[i].name] = plugins[i];
+		}
+		this.length = plugins.length;
+	}
+
 	/**
-	 * Item.
+	 * Returns an item.
 	 *
-	 * @param index Number.
+	 * @param index Index.
 	 * @returns Plugin.
 	 */
 	public item(index: number): Plugin {
-		return this[index];
+		return this[index] || null;
 	}
 
 	/**
-	 * NamedItem.
+	 * Returns an item.
 	 *
-	 * @param name String.
+	 * @param name Name.
 	 * @returns Plugin.
 	 */
 	public namedItem(name: string): Plugin {
-		return this[name];
-	}
-	/**
-	 * @returns Undefined.
-	 */
-	public refresh(): undefined {
-		return undefined;
+		return this[name] || null;
 	}
+
 	/**
-	 * @returns Number.
+	 * Refreshes the list.
 	 */
-	public get length(): number {
-		return this._length ? this._length : 0;
+	public refresh(): void {
+		// Do nothing
 	}
 
 	/**
+	 * Returns the object as a string.
+	 *
 	 * @returns String.
 	 */
 	public toString(): string {


Test patch:
diff --git a/packages/happy-dom/test/window/Window.test.ts b/packages/happy-dom/test/window/Window.test.ts
index 99aaed98..6b6810c7 100644
--- a/packages/happy-dom/test/window/Window.test.ts
+++ b/packages/happy-dom/test/window/Window.test.ts
@@ -5,6 +5,7 @@ import IHTMLScriptElement from '../../src/nodes/html-script-element/IHTMLScriptE
 import IFetchOptions from '../../src/window/IFetchOptions';
 import IResponse from '../../src/window/IResponse';
 import Window from '../../src/window/Window';
+import Navigator from '../../src/navigator/Navigator';
 
 describe('Window', () => {
 	let window: Window;
@@ -48,6 +49,41 @@ describe('Window', () => {
 		});
 	});
 
+	describe('get navigator()', () => {
+		it('Returns an instance of Navigator with browser data.', () => {
+			expect(window.navigator instanceof Navigator).toBe(true);
+			expect(window.navigator).toEqual({
+				appCodeName: 'Mozilla',
+				appName: 'Netscape',
+				appVersion: '5.0 (Windows)',
+				cookieEnabled: true,
+				credentials: null,
+				doNotTrack: 'unspecified',
+				geolocation: null,
+				hardwareConcurrency: 8,
+				language: 'en-US',
+				languages: ['en-US', 'en'],
+				locks: null,
+				maxTouchPoints: 0,
+				mimeTypes: {
+					length: 0
+				},
+				onLine: true,
+				permissions: null,
+				platform: 'Win32',
+				plugins: {
+					length: 0
+				},
+				product: 'Gecko',
+				productSub: '20100101',
+				userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:97.0) Gecko/20100101 Firefox/97.0',
+				vendor: '',
+				vendorSub: '',
+				webdriver: true
+			});
+		});
+	});
+
 	describe('getComputedStyle()', () => {
 		it('Returns a CSSStyleDeclaration object with computed styles that are live updated whenever the element styles are changed.', () => {
 			const element = <HTMLElement>window.document.createElement('div');