Skip to content

Commit

Permalink
feat: add FormData.prototype.forEach
Browse files Browse the repository at this point in the history
  • Loading branch information
KhafraDev authored and ronag committed Jun 12, 2022
1 parent 282a52b commit a6cc7e4
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 31 deletions.
26 changes: 26 additions & 0 deletions lib/fetch/formdata.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,32 @@ class FormData {
'FormData'
)
}

/**
* @param {(value: string, key: string, self: FormData) => void} callbackFn
* @param {unknown} thisArg
*/
forEach (callbackFn, thisArg = globalThis) {
if (!(this instanceof FormData)) {
throw new TypeError('Illegal invocation')
}

if (arguments.length < 1) {
throw new TypeError(
`Failed to execute 'forEach' on 'Headers': 1 argument required, but only ${arguments.length} present.`
)
}

if (typeof callbackFn !== 'function') {
throw new TypeError(
"Failed to execute 'forEach' on 'Headers': parameter 1 is not of type 'Function'."
)
}

for (const [key, value] of this) {
callbackFn.apply(thisArg, [value, key, this])
}
}
}

FormData.prototype[Symbol.iterator] = FormData.prototype.entries
Expand Down
59 changes: 59 additions & 0 deletions test/fetch/formdata.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,65 @@ test('formData.values', (t) => {
})
})

test('formData forEach', (t) => {
t.test('invalid arguments', (t) => {
t.throws(() => {
FormData.prototype.forEach.call({})
}, TypeError('Illegal invocation'))

t.throws(() => {
const fd = new FormData()

fd.forEach({})
}, TypeError)

t.end()
})

t.test('with a callback', (t) => {
const fd = new FormData()

fd.set('a', 'b')
fd.set('c', 'd')

let i = 0
fd.forEach((value, key, self) => {
if (i++ === 0) {
t.equal(value, 'b')
t.equal(key, 'a')
} else {
t.equal(value, 'd')
t.equal(key, 'c')
}

t.equal(fd, self)
})

t.end()
})

t.test('with a thisArg', (t) => {
const fd = new FormData()
fd.set('b', 'a')

fd.forEach(function (value, key, self) {
t.equal(this, globalThis)
t.equal(fd, self)
t.equal(key, 'b')
t.equal(value, 'a')
})

const thisArg = Symbol('thisArg')
fd.forEach(function () {
t.equal(this, thisArg)
}, thisArg)

t.end()
})

t.end()
})

test('formData toStringTag', (t) => {
const form = new FormData()
t.equal(form[Symbol.toStringTag], 'FormData')
Expand Down
10 changes: 5 additions & 5 deletions test/types/fetch.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import {
FormData,
Headers,
HeadersInit,
HeadersIterableIterator,
HeadersIterator,
SpecIterableIterator,
SpecIterator,
Request,
RequestCache,
RequestCredentials,
Expand Down Expand Up @@ -127,9 +127,9 @@ expectType<void>(headers.delete('key'))
expectType<string | null>(headers.get('key'))
expectType<boolean>(headers.has('key'))
expectType<void>(headers.set('key', 'value'))
expectType<HeadersIterableIterator<string>>(headers.keys())
expectType<HeadersIterableIterator<string>>(headers.values())
expectType<HeadersIterableIterator<[string, string]>>(headers.entries())
expectType<SpecIterableIterator<string>>(headers.keys())
expectType<SpecIterableIterator<string>>(headers.values())
expectType<SpecIterableIterator<[string, string]>>(headers.entries())

expectType<RequestCache>(request.cache)
expectType<RequestCredentials>(request.credentials)
Expand Down
10 changes: 5 additions & 5 deletions test/types/formdata.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Blob } from 'buffer'
import { Readable } from 'stream'
import { expectAssignable, expectType } from 'tsd'
import { File, FormData } from '../..'
import { File, FormData, SpecIterableIterator } from '../..'
import { DispatchOptions } from '../../types/dispatcher'

declare const dispatcherOptions: DispatchOptions
Expand All @@ -20,8 +20,8 @@ expectType<Array<File | string>>(formData.getAll('key'))
expectType<Array<File | string>>(formData.getAll('key'))
expectType<boolean>(formData.has('key'))
expectType<void>(formData.delete('key'))
expectAssignable<IterableIterator<string>>(formData.keys())
expectAssignable<IterableIterator<File | string>>(formData.values())
expectAssignable<IterableIterator<[string, File | string]>>(formData.entries())
expectAssignable<IterableIterator<[string, File | string]>>(formData[Symbol.iterator]())
expectAssignable<SpecIterableIterator<string>>(formData.keys())
expectAssignable<SpecIterableIterator<File | string>>(formData.values())
expectAssignable<SpecIterableIterator<[string, File | string]>>(formData.entries())
expectAssignable<SpecIterableIterator<[string, File | string]>>(formData[Symbol.iterator]())
expectAssignable<string | Buffer | Uint8Array | FormData | Readable | undefined | null>(dispatcherOptions.body)
20 changes: 10 additions & 10 deletions types/fetch.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,21 +38,21 @@ export interface BodyMixin {
readonly text: () => Promise<string>
}

export interface HeadersIterator<T, TReturn = any, TNext = undefined> {
export interface SpecIterator<T, TReturn = any, TNext = undefined> {
next(...args: [] | [TNext]): IteratorResult<T, TReturn>;
}

export interface HeadersIterableIterator<T> extends HeadersIterator<T> {
[Symbol.iterator](): HeadersIterableIterator<T>;
export interface SpecIterableIterator<T> extends SpecIterator<T> {
[Symbol.iterator](): SpecIterableIterator<T>;
}

export interface HeadersIterable<T> {
[Symbol.iterator](): HeadersIterator<T>;
export interface SpecIterable<T> {
[Symbol.iterator](): SpecIterator<T>;
}

export type HeadersInit = string[][] | Record<string, string | ReadonlyArray<string>> | Headers

export declare class Headers implements HeadersIterable<[string, string]> {
export declare class Headers implements SpecIterable<[string, string]> {
constructor (init?: HeadersInit)
readonly append: (name: string, value: string) => void
readonly delete: (name: string) => void
Expand All @@ -64,10 +64,10 @@ export declare class Headers implements HeadersIterable<[string, string]> {
thisArg?: unknown
) => void

readonly keys: () => HeadersIterableIterator<string>
readonly values: () => HeadersIterableIterator<string>
readonly entries: () => HeadersIterableIterator<[string, string]>
readonly [Symbol.iterator]: () => HeadersIterator<[string, string]>
readonly keys: () => SpecIterableIterator<string>
readonly values: () => SpecIterableIterator<string>
readonly entries: () => SpecIterableIterator<[string, string]>
readonly [Symbol.iterator]: () => SpecIterator<[string, string]>
}

export type RequestCache =
Expand Down
26 changes: 15 additions & 11 deletions types/formdata.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/// <reference types="node" />

import { File } from './file'
import { SpecIterator, SpecIterableIterator } from './fetch'

/**
* A `string` or `File` that represents a single value from a set of `FormData` key-value pairs.
Expand Down Expand Up @@ -73,32 +74,35 @@ export declare class FormData {
delete(name: string): void

/**
* Returns an [`iterator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) allowing to go through all keys contained in this `FormData` object.
* Each key is a `string`.
* Executes given callback function for each field of the FormData instance
*/
keys(): Generator<string>
forEach: (
callbackfn: (value: FormDataEntryValue, key: string, iterable: FormData) => void,
thisArg?: unknown
) => void

/**
* Returns an [`iterator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) allowing to go through the `FormData` key/value pairs.
* The key of each pair is a string; the value is a [`FormDataValue`](https://developer.mozilla.org/en-US/docs/Web/API/FormDataEntryValue).
* Returns an [`iterator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) allowing to go through all keys contained in this `FormData` object.
* Each key is a `string`.
*/
entries(): Generator<[string, FormDataEntryValue]>
keys: () => SpecIterableIterator<string>

/**
* Returns an [`iterator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) allowing to go through all values contained in this object `FormData` object.
* Each value is a [`FormDataValue`](https://developer.mozilla.org/en-US/docs/Web/API/FormDataEntryValue).
*/
values(): Generator<FormDataEntryValue>
values: () => SpecIterableIterator<FormDataEntryValue>

/**
* An alias for FormData#entries()
* Returns an [`iterator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) allowing to go through the `FormData` key/value pairs.
* The key of each pair is a string; the value is a [`FormDataValue`](https://developer.mozilla.org/en-US/docs/Web/API/FormDataEntryValue).
*/
[Symbol.iterator](): Generator<[string, FormDataEntryValue], void>
entries: () => SpecIterableIterator<[string, FormDataEntryValue]>

/**
* Executes given callback function for each field of the FormData instance
* An alias for FormData#entries()
*/
forEach(callback: (value: FormDataEntryValue, key: string, formData: FormData) => void, thisArg?: unknown): void
[Symbol.iterator]: () => SpecIterableIterator<[string, FormDataEntryValue]>

readonly [Symbol.toStringTag]: string
}

0 comments on commit a6cc7e4

Please sign in to comment.