Repository: capricorn86/happy-dom
Pull request #675 (2022-12-07 19:31:35)

#494@minor: Adds support for FileList to HTMLInputElement.


Patch:
diff --git a/packages/happy-dom/src/index.ts b/packages/happy-dom/src/index.ts
index ddd49e8e..88491aa0 100644
--- a/packages/happy-dom/src/index.ts
+++ b/packages/happy-dom/src/index.ts
@@ -123,6 +123,8 @@ import Attr from './nodes/attr/Attr';
 import IAttr from './nodes/attr/IAttr';
 import ProcessingInstruction from './nodes/processing-instruction/ProcessingInstruction';
 import IProcessingInstruction from './nodes/processing-instruction/IProcessingInstruction';
+import FileList from './nodes/html-input-element/FileList';
+import IFileList from './nodes/html-input-element/IFileList';
 
 export {
 	GlobalWindow,
@@ -250,5 +252,7 @@ export {
 	Attr,
 	IAttr,
 	ProcessingInstruction,
-	IProcessingInstruction
+	IProcessingInstruction,
+	FileList,
+	IFileList
 };
diff --git a/packages/happy-dom/src/nodes/html-input-element/FileList.ts b/packages/happy-dom/src/nodes/html-input-element/FileList.ts
new file mode 100644
index 00000000..52ee702e
--- /dev/null
+++ b/packages/happy-dom/src/nodes/html-input-element/FileList.ts
@@ -0,0 +1,35 @@
+import File from '../../file/File';
+import IFileList from './IFileList';
+
+/**
+ * FileList.
+ *
+ * @see https://developer.mozilla.org/en-US/docs/Web/API/FileList
+ */
+export default class FileList extends Array implements IFileList<File> {
+	/**
+	 * Constructor.
+	 */
+	constructor() {
+		super(0);
+	}
+
+	/**
+	 * Returns `Symbol.toStringTag`.
+	 *
+	 * @returns `Symbol.toStringTag`.
+	 */
+	public get [Symbol.toStringTag](): string {
+		return this.constructor.name;
+	}
+
+	/**
+	 * Returns item by index.
+	 *
+	 * @param index Index.
+	 * @returns Item.
+	 */
+	public item(index: number): File | null {
+		return this[index] || null;
+	}
+}
diff --git a/packages/happy-dom/src/nodes/html-input-element/HTMLInputElement.ts b/packages/happy-dom/src/nodes/html-input-element/HTMLInputElement.ts
index df4ad62f..c21c80bc 100644
--- a/packages/happy-dom/src/nodes/html-input-element/HTMLInputElement.ts
+++ b/packages/happy-dom/src/nodes/html-input-element/HTMLInputElement.ts
@@ -1,4 +1,3 @@
-import File from '../../file/File';
 import HTMLElement from '../html-element/HTMLElement';
 import ValidityState from '../validity-state/ValidityState';
 import DOMException from '../../exception/DOMException';
@@ -11,6 +10,9 @@ import IHTMLInputElement from './IHTMLInputElement';
 import IHTMLFormElement from '../html-form-element/IHTMLFormElement';
 import IHTMLElement from '../html-element/IHTMLElement';
 import HTMLInputElementValueStepping from './HTMLInputElementValueStepping';
+import FileList from './FileList';
+import File from '../../file/File';
+import IFileList from './IFileList';
 
 /**
  * HTML Input Element.
@@ -36,7 +38,7 @@ export default class HTMLInputElement extends HTMLElement implements IHTMLInputE
 	public defaultChecked = false;
 
 	// Type specific: file
-	public files: File[] = [];
+	public files: IFileList<File> = new FileList();
 
 	// Events
 	public oninput: (event: Event) => void | null = null;
@@ -974,7 +976,7 @@ export default class HTMLInputElement extends HTMLElement implements IHTMLInputE
 		clone._height = this._height;
 		clone._width = this._width;
 		clone.defaultChecked = this.defaultChecked;
-		clone.files = this.files.slice();
+		clone.files = <FileList>this.files.slice();
 		clone._selectionStart = this._selectionStart;
 		clone._selectionEnd = this._selectionEnd;
 		clone._selectionDirection = this._selectionDirection;
diff --git a/packages/happy-dom/src/nodes/html-input-element/IFileList.ts b/packages/happy-dom/src/nodes/html-input-element/IFileList.ts
new file mode 100644
index 00000000..78b86f78
--- /dev/null
+++ b/packages/happy-dom/src/nodes/html-input-element/IFileList.ts
@@ -0,0 +1,11 @@
+/**
+ * NodeList.
+ */
+export default interface IFileList<T> extends Array<T> {
+	/**
+	 * Returns item by index.
+	 *
+	 * @param index Index.
+	 */
+	item(index: number): T;
+}
diff --git a/packages/happy-dom/src/nodes/html-input-element/IHTMLInputElement.ts b/packages/happy-dom/src/nodes/html-input-element/IHTMLInputElement.ts
index d6919cdc..be276797 100644
--- a/packages/happy-dom/src/nodes/html-input-element/IHTMLInputElement.ts
+++ b/packages/happy-dom/src/nodes/html-input-element/IHTMLInputElement.ts
@@ -1,9 +1,10 @@
-import File from '../../file/File';
 import IHTMLElement from '../html-element/IHTMLElement';
 import IHTMLFormElement from '../html-form-element/IHTMLFormElement';
 import HTMLInputElementSelectionModeEnum from './HTMLInputElementSelectionModeEnum';
 import ValidityState from '../validity-state/ValidityState';
 import Event from '../../event/Event';
+import File from '../../file/File';
+import IFileList from './IFileList';
 
 /**
  * HTML Input Element.
@@ -17,7 +18,7 @@ export default interface IHTMLInputElement extends IHTMLElement {
 	formMethod: string;
 	formNoValidate: boolean;
 	defaultChecked: boolean;
-	files: File[];
+	files: IFileList<File>;
 	defaultValue: string;
 	height: number;
 	width: number;
diff --git a/packages/happy-dom/src/window/IWindow.ts b/packages/happy-dom/src/window/IWindow.ts
index fd3d7242..0c8c2ab3 100644
--- a/packages/happy-dom/src/window/IWindow.ts
+++ b/packages/happy-dom/src/window/IWindow.ts
@@ -104,6 +104,7 @@ import IElement from '../nodes/element/IElement';
 import ProcessingInstruction from '../nodes/processing-instruction/ProcessingInstruction';
 import IHappyDOMSettings from './IHappyDOMSettings';
 import RequestInfo from '../fetch/RequestInfo';
+import FileList from '../nodes/html-input-element/FileList';
 
 /**
  * Window without dependencies to server side specific packages.
@@ -218,6 +219,7 @@ export default interface IWindow extends IEventTarget, NodeJS.Global {
 	readonly XMLHttpRequest: typeof XMLHttpRequest;
 	readonly XMLHttpRequestUpload: typeof XMLHttpRequestUpload;
 	readonly XMLHttpRequestEventTarget: typeof XMLHttpRequestEventTarget;
+	readonly FileList: typeof FileList;
 
 	// Events
 	onload: (event: Event) => void;
diff --git a/packages/happy-dom/src/window/Window.ts b/packages/happy-dom/src/window/Window.ts
index fa9e3590..74103253 100644
--- a/packages/happy-dom/src/window/Window.ts
+++ b/packages/happy-dom/src/window/Window.ts
@@ -115,6 +115,7 @@ import IElement from '../nodes/element/IElement';
 import ProcessingInstruction from '../nodes/processing-instruction/ProcessingInstruction';
 import IHappyDOMSettings from './IHappyDOMSettings';
 import RequestInfo from '../fetch/RequestInfo';
+import FileList from '../nodes/html-input-element/FileList';
 
 const ORIGINAL_SET_TIMEOUT = setTimeout;
 const ORIGINAL_CLEAR_TIMEOUT = clearTimeout;
@@ -246,6 +247,7 @@ export default class Window extends EventTarget implements IWindow {
 	public readonly MimeTypeArray = MimeTypeArray;
 	public readonly Plugin = Plugin;
 	public readonly PluginArray = PluginArray;
+	public readonly FileList = FileList;
 	public readonly Headers: { new (init?: IHeadersInit): IHeaders } = Headers;
 	public readonly DOMRect: typeof DOMRect;
 	public readonly Request: {


Test patch:
diff --git a/packages/happy-dom/test/nodes/html-input-element/FileList.test.ts b/packages/happy-dom/test/nodes/html-input-element/FileList.test.ts
new file mode 100644
index 00000000..c8e0e9b6
--- /dev/null
+++ b/packages/happy-dom/test/nodes/html-input-element/FileList.test.ts
@@ -0,0 +1,33 @@
+import Window from '../../../src/window/Window';
+import IWindow from '../../../src/window/Window';
+import IDocument from '../../../src/nodes/document/IDocument';
+import File from '../../../src/file/File';
+import IHTMLInputElement from '../../../src/nodes/html-input-element/IHTMLInputElement';
+
+describe('FileList', () => {
+	let window: IWindow;
+	let document: IDocument;
+
+	beforeEach(() => {
+		window = new Window();
+		document = window.document;
+	});
+
+	afterEach(() => {
+		jest.restoreAllMocks();
+	});
+
+	describe('item()', () => {
+		it('Returns file at index.', () => {
+			const element = <IHTMLInputElement>document.createElement('input');
+			const file1 = new File([''], 'file.txt');
+			const file2 = new File([''], 'file2.txt');
+
+			element.files.push(file1);
+			element.files.push(file2);
+
+			expect(element.files.item(0)).toBe(file1);
+			expect(element.files.item(1)).toBe(file2);
+		});
+	});
+});