From 901b70196f23639417fb44416073025776bbc23b Mon Sep 17 00:00:00 2001 From: David Ortner Date: Wed, 7 Dec 2022 20:29:24 +0100 Subject: [PATCH] #494@minor: Adds support for FileList to HTMLInputElement. --- packages/happy-dom/src/index.ts | 6 +++- .../src/nodes/html-input-element/FileList.ts | 35 +++++++++++++++++++ .../html-input-element/HTMLInputElement.ts | 8 +++-- .../src/nodes/html-input-element/IFileList.ts | 11 ++++++ .../html-input-element/IHTMLInputElement.ts | 5 +-- packages/happy-dom/src/window/IWindow.ts | 2 ++ packages/happy-dom/src/window/Window.ts | 2 ++ .../nodes/html-input-element/FileList.test.ts | 33 +++++++++++++++++ 8 files changed, 96 insertions(+), 6 deletions(-) create mode 100644 packages/happy-dom/src/nodes/html-input-element/FileList.ts create mode 100644 packages/happy-dom/src/nodes/html-input-element/IFileList.ts create mode 100644 packages/happy-dom/test/nodes/html-input-element/FileList.test.ts diff --git a/packages/happy-dom/src/index.ts b/packages/happy-dom/src/index.ts index ddd49e8e9..88491aa06 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 000000000..52ee702ef --- /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 { + /** + * 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 df4ad62f9..c21c80bc6 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 = 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 = 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 000000000..78b86f780 --- /dev/null +++ b/packages/happy-dom/src/nodes/html-input-element/IFileList.ts @@ -0,0 +1,11 @@ +/** + * NodeList. + */ +export default interface IFileList extends Array { + /** + * 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 d6919cdcc..be2767970 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; 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 fd3d72424..0c8c2ab39 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 fa9e35909..741032532 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: { 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 000000000..c8e0e9b6e --- /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 = 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); + }); + }); +});