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) {
}
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
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) {
}
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
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
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
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
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
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
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'])
})