import { test, expect } from 'vitest' import { forEach, map, find, findIndex, some, every, filter, reduce, forEachSeries, mapSeries, findSeries, findIndexSeries, someSeries, everySeries, filterSeries } from '../src/pIteration.js' const delay = (ms?: number) => new Promise(resolve => setTimeout(() => resolve(ms), ms || 0)) test('forEach, check callbacks are run in parallel', async () => { let total = 0 const parallelCheck: number[] = [] await forEach([2, 1, 3], async (num: number, index: number, array: number[]) => { await delay(num * 100) expect(array[index]).toBe(num) parallelCheck.push(num) total += num }) expect(parallelCheck).toEqual([1, 2, 3]) expect(total).toBe(6) }) test('forEach passing a non-async function', async () => { let total = 0 await forEach([2, 1, 3], (num: number, index: number, array: number[]) => { expect(array[index]).toBe(num) total += num }) expect(total).toBe(6) }) test('forEach unwraps Promises in the array', async () => { let total = 0 await forEach([2, Promise.resolve(1), 3], async (num: number, index: number, array: number[]) => { expect(await Promise.resolve(array[index])).toBe(num) total += num }) expect(total).toBe(6) }) test('forEach should execute callbacks as soon as Promises are unwrapped', async () => { const parallelCheck: number[] = [] await forEach([delay(500), delay(300), delay(400)], (num: number) => { parallelCheck.push(num) }) expect(parallelCheck).toEqual([300, 400, 500]) }) test('forEach, throw inside callback', async function () { await expect(forEach([2, 1, 3], () => { throw new Error('test') })).rejects.toThrow('test') }) test('forEach using thisArg', async () => { let total = 0 const testObj = { test: 1 } await forEach([1, 2, 3], async function (this: unknown, num: number, index: number, array: number[]) { await delay() expect(array[index]).toBe(num) expect(this).toEqual(testObj) total += num }, testObj) expect(total).toBe(6) }) test('forEach used with promises in a non-async function', async () => { let total = 0 try { await forEach([1, 2, 3], async function (num: number, index: number, array: number[]) { await delay() expect(array[index]).toBe(num) total += num }) } catch (err) { // ignore } expect(total).toBe(6) }) test('forEach should not execute any callback if array is empty', async () => { let count = 0 await forEach([], async () => { await delay() count++ }) expect(count).toBe(0) }) test('forEach should skip holes in arrays', async () => { let count = 0 // eslint-disable-next-line no-sparse-arrays await forEach([0, 1, 2, , 5, ,], async () => { count++ }) expect(count).toBe(4) }) test('map, check callbacks are run in parallel', async () => { const parallelCheck: number[] = [] const arr = await map([3, 1, 2], async (num: number, index: number, array: number[]) => { await delay(num * 100) expect(array[index]).toBe(num) parallelCheck.push(num) return num * 2 }) expect(arr).toEqual([6, 2, 4]) expect(parallelCheck).toEqual([1, 2, 3]) }) test('map unwraps Promises in the array', async () => { const arr = await map([2, Promise.resolve(1), 3], async (num: number, index: number, array: number[]) => { expect(await Promise.resolve(array[index])).toBe(num) return num * 2 }) expect(arr).toEqual([4, 2, 6]) }) test('map should execute callbacks as soon as Promises are unwrapped', async () => { const parallelCheck: number[] = [] await map([delay(500), delay(300), delay(400)], (num: number) => { parallelCheck.push(num) }) expect(parallelCheck).toEqual([300, 400, 500]) }) test('map passing a non-async function that return a promise', async () => { const arr = await map([1, 2, 3], (num: number) => Promise.resolve(num * 2)) expect(arr).toEqual([2, 4, 6]) }) test('map, throw inside callback', async function () { await expect(map([2, 1, 3], () => { throw new Error('test') })).rejects.toThrow('test') }) test('map used with promises in a non-async function', async () => { let result: number[] = [] try { result = await map([1, 2, 3], async function (num: number) { await delay() return num * 2 }) } catch (err) { // ignore } expect(result).toEqual([2, 4, 6]) }) test('map should return an empty array if passed array is empty', async () => { const count = 0 const arr = await map([], async () => { await delay() return 3 }) expect(arr).toEqual([]) expect(count).toEqual(0) }) test('map should skip holes in arrays', async () => { let count = 0 // eslint-disable-next-line no-sparse-arrays await map([0, 1, 2, , 5, ,], async () => { count++ }) expect(count).toBe(4) }) test('find', async () => { const foundNum = await find([1, 2, 3], async (num: number, index: number, array: number[]) => { await delay() expect(array[index]).toBe(num) return num === 2 }) expect(foundNum).toBe(2) }) test('find, throw inside callback', async function () { await expect(find([2, 1, 3], () => { throw new Error('test') })).rejects.toThrow('test') }) test('find returns undefined if did not find anything', async () => { const foundNum = await find([1, 2], async () => { await delay() return false }) expect(foundNum).toBe(undefined) }) test('find returns undefined if array is empty', async () => { const foundNum = await find([], async () => { await delay() return false }) expect(foundNum).toBe(undefined) }) test('find unwraps Promises in the array', async () => { const foundNum = await find([1, Promise.resolve(2), 3], async (num: number, index: number, array: number[]) => { await delay() expect(await Promise.resolve(array[index])).toBe(num) return num === 2 }) expect(foundNum).toBe(2) }) test('find should execute callbacks as soon as Promises are unwrapped', async () => { const parallelCheck: number[] = [] await find([delay(500), delay(300), delay(400)], (num: number) => { parallelCheck.push(num) }) expect(parallelCheck).toEqual([300, 400, 500]) }) test('find passing a non-async callback', async () => { const foundNum = await find([1, 2, 3], (num: number, index: number, array: number[]) => { expect(array[index]).toBe(num) return num === 2 }) expect(foundNum).toBe(2) }) test('find should not skip holes in arrays', async () => { let count = 0 // eslint-disable-next-line no-sparse-arrays await find([0, 1, 2, , 5, ,], async () => { count++ }) expect(count).toBe(6) }) test('findIndex', async () => { const foundIndex = await findIndex([1, 2, 3], async (num: number, index: number, array: number[]) => { await delay() expect(array[index]).toBe(num) return num === 2 }) expect(foundIndex).toBe(1) }) test('findIndex, throw inside callback', async function () { await expect(findIndex([2, 1, 3], () => { throw new Error('test') })).rejects.toThrow('test') }) test('findIndex returns -1 if did not find anything', async () => { const notFound = await findIndex([1, 2], async () => { await delay() return false }) expect(notFound).toBe(-1) }) test('findIndex returns -1 if array is empty', async () => { const notFound = await findIndex([], async () => { await delay() return false }) expect(notFound).toBe(-1) }) test('findIndex unwraps Promises in the array', async () => { const foundIndex = await findIndex([Promise.resolve(1), 2, 3], async (num: number, index: number, array: number[]) => { await delay() expect(await Promise.resolve(array[index])).toBe(num) return num === 2 }) expect(foundIndex).toBe(1) }) test('findIndex should execute callbacks as soon as Promises are unwrapped', async () => { const parallelCheck: number[] = [] await findIndex([delay(500), delay(300), delay(400)], (num: number) => { parallelCheck.push(num) }) expect(parallelCheck).toEqual([300, 400, 500]) }) test('findIndex should not skip holes in arrays', async () => { let count = 0 // eslint-disable-next-line no-sparse-arrays await findIndex([0, 1, 2, , 5, ,], async () => { count++ }) expect(count).toBe(6) }) test('some', async () => { const isIncluded = await some([1, 2, 3], async (num: number, index: number, array: number[]) => { await delay() expect(array[index]).toBe(num) return num === 3 }) expect(isIncluded).toBe(true) }) test('some, throw inside callback', async function () { await expect(some([2, 1, 3], () => { throw new Error('test') })).rejects.toThrow('test') }) test('some should iterate until the end', async () => { const isIncluded = await some([500, 200, 400], async (num: number, index: number, array: number[]) => { await delay(num) expect(array[index]).toBe(num) return num === 500 }) expect(isIncluded).toBe(true) }) test('some unwraps Promises in the array', async () => { const isIncluded = await some([1, Promise.resolve(2), 3], async (num: number, index: number, array: number[]) => { await delay() expect(await Promise.resolve(array[index])).toBe(num) return num === 3 }) expect(isIncluded).toBe(true) }) test('some should execute callbacks as soon as Promises are unwrapped', async () => { const parallelCheck: number[] = [] await some([delay(500), delay(300), delay(400)], (num: number) => { parallelCheck.push(num) }) expect(parallelCheck).toEqual([300, 400, 500]) }) test('some passing a non-async callback', async () => { const isIncluded = await some([1, 2, 3], (num: number, index: number, array: number[]) => { expect(array[index]).toBe(num) return num === 3 }) expect(isIncluded).toBe(true) }) test('some (return false)', async () => { const isIncluded = await some([1, 2, 3], async (num: number, index: number, array: number[]) => { await delay() expect(array[index]).toBe(num) return num === 4 }) expect(isIncluded).toBe(false) }) test('some with empty array should return false', async () => { const isIncluded = await some([], async () => { await delay() return false }) expect(isIncluded).toBe(false) }) test('some should skip holes in arrays', async () => { let count = 0 // eslint-disable-next-line no-sparse-arrays await some([0, 1, 2, , 5, ,], async () => { count++ }) expect(count).toBe(4) }) test('every', async () => { const allIncluded = await every([1, 2, 3], async (num: number, index: number, array: number[]) => { await delay() expect(array[index]).toBe(num) return typeof num === 'number' }) expect(allIncluded).toBe(true) }) test('every, throw inside callback', async function () { await expect(every([2, 1, 3], () => { throw new Error('test') })).rejects.toThrow('test') }) test('every should iterate until the end', async () => { const isIncluded = await every([500, 200, 400], async (num: number, index: number, array: number[]) => { await delay(num) expect(array[index]).toBe(num) return true }) expect(isIncluded).toBe(true) }) test('every unwraps Promises in the array', async () => { const allIncluded = await every([Promise.resolve(1), 2, 3], async (num: number, index: number, array: number[]) => { await delay() expect(await Promise.resolve(array[index])).toBe(num) return typeof num === 'number' }) expect(allIncluded).toBe(true) }) test('every should execute callbacks as soon as Promises are unwrapped', async () => { const parallelCheck: number[] = [] await every([delay(500), delay(300), delay(400)], (num: number) => { parallelCheck.push(num) return true }) expect(parallelCheck).toEqual([300, 400, 500]) }) test('every passing a non-async callback', async () => { const allIncluded = await every([1, 2, 3], (num: number, index: number, array: number[]) => { expect(array[index]).toBe(num) return typeof num === 'number' }) expect(allIncluded).toBe(true) }) test('every (return false)', async () => { const allIncluded = await every([1, 2, '3'], async (num: number, index: number, array: number[]) => { await delay() expect(array[index]).toBe(num) return typeof num === 'number' }) expect(allIncluded).toBe(false) }) test('every with empty array should return true', async () => { const allIncluded = await every([], async () => { await delay() return false }) expect(allIncluded).toBe(true) }) test('every should skip holes in arrays', async () => { let count = 0 // eslint-disable-next-line no-sparse-arrays const allIncluded = await every([0, 1, 2, , 5, ,], async () => { count++ return true }) expect(allIncluded).toBe(true) expect(count).toBe(4) }) test('filter', async () => { const numbers = await filter([2, 1, '3', 4, '5'], async (num: number) => { await delay(num * 100) return typeof num === 'number' }) expect(numbers).toEqual([2, 1, 4]) }) test('filter should skip holes in arrays', async () => { let count = 0 // eslint-disable-next-line no-sparse-arrays const numbers = await filter([0, 1, 2, '3', , 5, '6', ,], async (num: number) => { await delay(num * 100) count++ return typeof num === 'number' }) expect(count).toBe(6) expect(numbers).toEqual([0, 1, 2, 5]) }) test('filter, check callbacks are run in parallel', async () => { const parallelCheck: number[] = [] const numbers = await filter([2, 1, '3'], async (num: number, index: number, array: number[]) => { await delay(num * 100) expect(array[index]).toBe(num) if (typeof num === 'number') { parallelCheck.push(num) return true } }) expect(parallelCheck).toEqual([1, 2]) expect(numbers).toEqual([2, 1]) }) test('filter, throw inside callback', async function () { await expect(filter([2, 1, 3], () => { throw new Error('test') })).rejects.toThrow('test') }) test('filter unwraps Promises in the array', async () => { const parallelCheck: number[] = [] const numbers = await filter([Promise.resolve(2), 1, '3'], async (num: number, index: number, array: number[]) => { await delay(num * 100) expect(await Promise.resolve(array[index])).toBe(num) if (typeof num === 'number') { parallelCheck.push(num) return true } }) expect(parallelCheck).toEqual([1, 2]) expect(numbers).toEqual([2, 1]) }) test('filter should execute callbacks as soon as Promises are unwrapped', async () => { const parallelCheck: number[] = [] await filter([delay(500), delay(300), delay(400)], (num: number) => { parallelCheck.push(num) }) expect(parallelCheck).toEqual([300, 400, 500]) }) test('filter should return an empty array if passed array is empty', async () => { let count = 0 const empty = await filter([], async () => { await delay() count++ return true }) expect(count).toEqual(0) expect(empty).toEqual([]) }) test('reduce with initialValue', async () => { const sum = await reduce([1, 2, 3], async (accumulator: number, currentValue: number, index: number, array: number[]) => { await delay() expect(array[index]).toBe(currentValue) return accumulator + currentValue }, 1) expect(sum).toBe(7) }) test('reduce with falsy initialValue', async () => { const sum = await reduce(['1', '2', '3'], async (accumulator: number, currentValue: number, index: number, array: number[]) => { await delay() expect(array[index]).toBe(currentValue) return accumulator + Number(currentValue) }, 0) expect(sum).toBe(6) const string = await reduce([1, 2, 3], async (accumulator: number, currentValue: number, index: number, array: number[]) => { await delay() expect(array[index]).toBe(currentValue) return accumulator + String(currentValue) }, '') expect(string, '1).toBe(') const somePositive = await reduce([-1, 2, 3], async (accumulator: number, currentValue: number, index: number, array: number[]) => { await delay() expect(array[index]).toBe(currentValue) return accumulator ? accumulator : currentValue > 0 }, false) expect(somePositive).toBe(true) }) test('reduce, throw inside callback', async function () { await expect(reduce([2, 1, 3], () => { throw new Error('test') })).rejects.toThrow('test') }) test('reduce unwrap Promises in the array', async () => { const sum = await reduce([Promise.resolve(1), 2, 3], async (accumulator: number, currentValue: number, index: number, array: number[]) => { await delay() expect(await Promise.resolve(array[index])).toBe(currentValue) return accumulator + currentValue }, 1) expect(sum).toBe(7) }) test('reduce unwrap Promises in the initialValue', async () => { const sum = await reduce([1, 2, 3], async (accumulator: number, currentValue: number, index: number, array: number[]) => { await delay() expect(await Promise.resolve(array[index])).toBe(currentValue) return accumulator + currentValue }, Promise.resolve(1)) expect(sum).toBe(7) }) test('reduce without initialValue', async () => { const sum = await reduce([1, 2, 3], async (accumulator: number, currentValue: number, index: number, array: number[]) => { await delay() expect(array[index]).toBe(currentValue) return accumulator + currentValue }) expect(sum).toBe(6) }) test('reduce of array with two elements without initialValue', async () => { const sum = await reduce([1, 2], async (accumulator: number, currentValue: number, index: number, array: number[]) => { await delay() expect(array[index]).toBe(currentValue) return accumulator + currentValue }) expect(sum).toBe(3) }) test('reduce of empty array without initialValue should throw TypeError', async () => { await expect( reduce([], async (accumulator: number, currentValue: number) => { await delay() return accumulator + currentValue }) ).rejects.toThrow('Reduce of empty array with no initial value') }) test('reduce of empty array with initialValue should return initialValue', async () => { let count = 0 const sum = await reduce([], async (accumulator: number, currentValue: number) => { await delay() count++ return accumulator + currentValue }, 6) expect(count).toBe(0) expect(sum).toBe(6) }) test('reduce of array with one element and no initialValue should return that element', async () => { let count = 0 const sum = await reduce([6], async (accumulator: number, currentValue: number) => { await delay() count++ return accumulator + currentValue }) expect(count).toBe(0) expect(sum).toBe(6) }) test('forEachSeries', async () => { let total = 0 const seriesCheck: number[] = [] await forEachSeries([2, 1, 3], async (num: number, index: number, array: number[]) => { await delay(num * 100) expect(array[index]).toBe(num) seriesCheck.push(num) total += num }) expect(seriesCheck).toEqual([2, 1, 3]) expect(total).toBe(6) }) test('mapSeries', async () => { const seriesCheck: number[] = [] const arr = await mapSeries([3, 1, 2], async (num: number, index: number, array: number[]) => { await delay(num * 100) expect(array[index]).toBe(num) seriesCheck.push(num) return num * 2 }) expect(arr).toEqual([6, 2, 4]) expect(seriesCheck).toEqual([3, 1, 2]) }) test('findSeries', async () => { const seriesCheck: number[] = [] const foundNum = await findSeries([3, 1, 2], async (num: number, index: number, array: number[]) => { await delay(num * 100) expect(array[index]).toBe(num) seriesCheck.push(num) return num === 2 }) expect(foundNum).toBe(2) expect(seriesCheck).toEqual([3, 1, 2]) }) test('findIndexSeries', async () => { const seriesCheck: number[] = [] const foundNum = await findIndexSeries([3, 1, 2], async (num: number, index: number, array: number[]) => { await delay(num * 100) expect(array[index]).toBe(num) seriesCheck.push(num) return num === 2 }) expect(foundNum).toBe(2) expect(seriesCheck).toEqual([3, 1, 2]) }) test('someSeries', async () => { const seriesCheck: number[] = [] const isIncluded = await someSeries([3, 1, 2], async (num: number, index: number, array: number[]) => { await delay(num * 100) expect(array[index]).toBe(num) seriesCheck.push(num) return num === 2 }) expect(isIncluded).toBe(true) expect(seriesCheck).toEqual([3, 1, 2]) }) test('everySeries', async () => { const seriesCheck: number[] = [] const allIncluded = await everySeries([3, 1, 2], async (num: number, index: number, array: number[]) => { await delay(num * 100) expect(array[index]).toBe(num) seriesCheck.push(num) return typeof num === 'number' }) expect(allIncluded).toBe(true) expect(seriesCheck).toEqual([3, 1, 2]) }) test('filterSeries', async () => { const seriesCheck: number[] = [] const numbers = await filterSeries([2, 1, '3'], async (num: number) => { await delay(num * 100) seriesCheck.push(num) return typeof num === 'number' }) expect(numbers).toEqual([2, 1]) expect(seriesCheck).toEqual([2, 1, '3']) })