From 583d1cc8ea8aa7bb4a107c49cbde7c7d99309b35 Mon Sep 17 00:00:00 2001 From: Nev Date: Wed, 25 Feb 2026 22:35:26 -0800 Subject: [PATCH] feat(array): add new array helpers and array-like detection - add arrUnique, arrCompact, arrFlatten, arrGroupBy, arrChunk with ArrayLike support - add isArrayLike helper and export in index - add tests for new array helpers and isArrayLike --- .size-limit.json | 10 +- README.md | 6 +- lib/src/array/chunk.ts | 56 ++++++ lib/src/array/compact.ts | 46 +++++ lib/src/array/flatten.ts | 67 ++++++++ lib/src/array/groupBy.ts | 68 ++++++++ lib/src/array/unique.ts | 55 ++++++ lib/src/helpers/base.ts | 28 ++- lib/src/helpers/customError.ts | 2 +- lib/src/index.ts | 7 +- lib/src/internal/unwrapFunction.ts | 4 +- lib/test/bundle-size-check.js | 4 +- lib/test/src/common/helpers/array.test.ts | 201 ++++++++++++++++++++++ lib/test/src/common/helpers/base.test.ts | 71 +++++++- 14 files changed, 609 insertions(+), 16 deletions(-) create mode 100644 lib/src/array/chunk.ts create mode 100644 lib/src/array/compact.ts create mode 100644 lib/src/array/flatten.ts create mode 100644 lib/src/array/groupBy.ts create mode 100644 lib/src/array/unique.ts diff --git a/.size-limit.json b/.size-limit.json index a014aba9..3606bff0 100644 --- a/.size-limit.json +++ b/.size-limit.json @@ -2,14 +2,14 @@ { "name": "es5-full", "path": "lib/dist/es5/mod/ts-utils.js", - "limit": "22.5 kb", + "limit": "23.5 kb", "brotli": false, "running": false }, { "name": "es6-full", "path": "lib/dist/es6/mod/ts-utils.js", - "limit": "21.5 kb", + "limit": "22.5 kb", "brotli": false, "running": false }, @@ -23,21 +23,21 @@ { "name": "es6-full-brotli", "path": "lib/dist/es6/mod/ts-utils.js", - "limit": "8 kb", + "limit": "8.5 kb", "brotli": true, "running": false }, { "name": "es5-zip", "path": "lib/dist/es5/mod/ts-utils.js", - "limit": "9 Kb", + "limit": "9.5 Kb", "gzip": true, "running": false }, { "name": "es6-zip", "path": "lib/dist/es6/mod/ts-utils.js", - "limit": "9 Kb", + "limit": "9.5 Kb", "gzip": true, "running": false }, diff --git a/README.md b/README.md index 52d17cc2..9fd4f29d 100644 --- a/README.md +++ b/README.md @@ -105,12 +105,12 @@ Below is a categorized list of all available utilities with direct links to thei | Type | Functions / Helpers / Aliases / Polyfills |----------------------------|--------------------------------------------------- | Runtime Environment Checks | [getCancelIdleCallback](https://nevware21.github.io/ts-utils/typedoc/functions/getCancelIdleCallback.html)(); [getDocument](https://nevware21.github.io/ts-utils/typedoc/functions/getDocument.html)(); [getGlobal](https://nevware21.github.io/ts-utils/typedoc/functions/getGlobal.html)(); [getHistory](https://nevware21.github.io/ts-utils/typedoc/functions/getHistory.html)(); [getIdleCallback](https://nevware21.github.io/ts-utils/typedoc/functions/getIdleCallback.html)(); [getInst](https://nevware21.github.io/ts-utils/typedoc/functions/getInst.html)(); [getNavigator](https://nevware21.github.io/ts-utils/typedoc/functions/getNavigator.html)(); [getPerformance](https://nevware21.github.io/ts-utils/typedoc/functions/getPerformance.html)(); [getWindow](https://nevware21.github.io/ts-utils/typedoc/functions/getWindow.html)(); [hasDocument](https://nevware21.github.io/ts-utils/typedoc/functions/hasDocument.html)(); [hasHistory](https://nevware21.github.io/ts-utils/typedoc/functions/hasHistory.html)(); [hasNavigator](https://nevware21.github.io/ts-utils/typedoc/functions/hasNavigator.html)(); [hasPerformance](https://nevware21.github.io/ts-utils/typedoc/functions/hasPerformance.html)(); [hasWindow](https://nevware21.github.io/ts-utils/typedoc/functions/hasWindow.html)(); [isNode](https://nevware21.github.io/ts-utils/typedoc/functions/isNode.html)(); [isWebWorker](https://nevware21.github.io/ts-utils/typedoc/functions/isWebWorker.html)(); [hasIdleCallback](https://nevware21.github.io/ts-utils/typedoc/functions/hasIdleCallback.html)(); [lazySafeGetInst](https://nevware21.github.io/ts-utils/typedoc/functions/lazySafeGetInst.html)(); -| Type Identity | [isArray](https://nevware21.github.io/ts-utils/typedoc/functions/isArray.html)(); [isArrayBuffer](https://nevware21.github.io/ts-utils/typedoc/functions/isArrayBuffer.html)(); [isAsyncFunction](https://nevware21.github.io/ts-utils/typedoc/functions/isAsyncFunction.html)(); [isAsyncGenerator](https://nevware21.github.io/ts-utils/typedoc/functions/isAsyncGenerator.html)(); [isBigInt](https://nevware21.github.io/ts-utils/typedoc/functions/isBigInt.html)(); [isBlob](https://nevware21.github.io/ts-utils/typedoc/functions/isBlob.html)(); [isBoolean](https://nevware21.github.io/ts-utils/typedoc/functions/isBoolean.html)(); [isDate](https://nevware21.github.io/ts-utils/typedoc/functions/isDate.html)(); [isElement](https://nevware21.github.io/ts-utils/typedoc/functions/isElement.html)(); [isElementLike](https://nevware21.github.io/ts-utils/typedoc/functions/isElementLike.html)(); [isError](https://nevware21.github.io/ts-utils/typedoc/functions/isError.html)(); [isFile](https://nevware21.github.io/ts-utils/typedoc/functions/isFile.html)(); [isFiniteNumber](https://nevware21.github.io/ts-utils/typedoc/functions/isFiniteNumber.html)(); [isFormData](https://nevware21.github.io/ts-utils/typedoc/functions/isFormData.html)(); [isFunction](https://nevware21.github.io/ts-utils/typedoc/functions/isFunction.html)(); [isGenerator](https://nevware21.github.io/ts-utils/typedoc/functions/isGenerator.html)(); [isInteger](https://nevware21.github.io/ts-utils/typedoc/functions/isInteger.html)(); [isIterable](https://nevware21.github.io/ts-utils/typedoc/functions/isIterable.html)(); [isIterator](https://nevware21.github.io/ts-utils/typedoc/functions/isIterator.html)(); [isMap](https://nevware21.github.io/ts-utils/typedoc/functions/isMap.html)(); [isMapLike](https://nevware21.github.io/ts-utils/typedoc/functions/isMapLike.html)(); [isNullOrUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isNullOrUndefined.html)(); [isNumber](https://nevware21.github.io/ts-utils/typedoc/functions/isNumber.html)(); [isObject](https://nevware21.github.io/ts-utils/typedoc/functions/isObject.html)(); [isPlainObject](https://nevware21.github.io/ts-utils/typedoc/functions/isPlainObject.html)(); [isPrimitive](https://nevware21.github.io/ts-utils/typedoc/functions/isPrimitive.html)(); [isPrimitiveType](https://nevware21.github.io/ts-utils/typedoc/functions/isPrimitiveType.html)(); [isPromise](https://nevware21.github.io/ts-utils/typedoc/functions/isPromise.html)(); [isPromiseLike](https://nevware21.github.io/ts-utils/typedoc/functions/isPromiseLike.html)(); [isRegExp](https://nevware21.github.io/ts-utils/typedoc/functions/isRegExp.html)(); [isSet](https://nevware21.github.io/ts-utils/typedoc/functions/isSet.html)(); [isSetLike](https://nevware21.github.io/ts-utils/typedoc/functions/isSetLike.html)(); [isStrictNullOrUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isStrictNullOrUndefined.html)(); [isStrictUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isStrictUndefined.html)(); [isString](https://nevware21.github.io/ts-utils/typedoc/functions/isString.html)(); [isThenable](https://nevware21.github.io/ts-utils/typedoc/functions/isThenable.html)(); [isTypeof](https://nevware21.github.io/ts-utils/typedoc/functions/isTypeof.html)(); [isUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isUndefined.html)(); [isWeakMap](https://nevware21.github.io/ts-utils/typedoc/functions/isWeakMap.html)(); [isWeakSet](https://nevware21.github.io/ts-utils/typedoc/functions/isWeakSet.html)(); +| Type Identity | [isArray](https://nevware21.github.io/ts-utils/typedoc/functions/isArray.html)(); [isArrayLike](https://nevware21.github.io/ts-utils/typedoc/functions/isArrayLike.html)(); [isArrayBuffer](https://nevware21.github.io/ts-utils/typedoc/functions/isArrayBuffer.html)(); [isAsyncFunction](https://nevware21.github.io/ts-utils/typedoc/functions/isAsyncFunction.html)(); [isAsyncGenerator](https://nevware21.github.io/ts-utils/typedoc/functions/isAsyncGenerator.html)(); [isBigInt](https://nevware21.github.io/ts-utils/typedoc/functions/isBigInt.html)(); [isBlob](https://nevware21.github.io/ts-utils/typedoc/functions/isBlob.html)(); [isBoolean](https://nevware21.github.io/ts-utils/typedoc/functions/isBoolean.html)(); [isDate](https://nevware21.github.io/ts-utils/typedoc/functions/isDate.html)(); [isElement](https://nevware21.github.io/ts-utils/typedoc/functions/isElement.html)(); [isElementLike](https://nevware21.github.io/ts-utils/typedoc/functions/isElementLike.html)(); [isError](https://nevware21.github.io/ts-utils/typedoc/functions/isError.html)(); [isFile](https://nevware21.github.io/ts-utils/typedoc/functions/isFile.html)(); [isFiniteNumber](https://nevware21.github.io/ts-utils/typedoc/functions/isFiniteNumber.html)(); [isFormData](https://nevware21.github.io/ts-utils/typedoc/functions/isFormData.html)(); [isFunction](https://nevware21.github.io/ts-utils/typedoc/functions/isFunction.html)(); [isGenerator](https://nevware21.github.io/ts-utils/typedoc/functions/isGenerator.html)(); [isInteger](https://nevware21.github.io/ts-utils/typedoc/functions/isInteger.html)(); [isIterable](https://nevware21.github.io/ts-utils/typedoc/functions/isIterable.html)(); [isIterator](https://nevware21.github.io/ts-utils/typedoc/functions/isIterator.html)(); [isMap](https://nevware21.github.io/ts-utils/typedoc/functions/isMap.html)(); [isMapLike](https://nevware21.github.io/ts-utils/typedoc/functions/isMapLike.html)(); [isNullOrUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isNullOrUndefined.html)(); [isNumber](https://nevware21.github.io/ts-utils/typedoc/functions/isNumber.html)(); [isObject](https://nevware21.github.io/ts-utils/typedoc/functions/isObject.html)(); [isPlainObject](https://nevware21.github.io/ts-utils/typedoc/functions/isPlainObject.html)(); [isPrimitive](https://nevware21.github.io/ts-utils/typedoc/functions/isPrimitive.html)(); [isPrimitiveType](https://nevware21.github.io/ts-utils/typedoc/functions/isPrimitiveType.html)(); [isPromise](https://nevware21.github.io/ts-utils/typedoc/functions/isPromise.html)(); [isPromiseLike](https://nevware21.github.io/ts-utils/typedoc/functions/isPromiseLike.html)(); [isRegExp](https://nevware21.github.io/ts-utils/typedoc/functions/isRegExp.html)(); [isSet](https://nevware21.github.io/ts-utils/typedoc/functions/isSet.html)(); [isSetLike](https://nevware21.github.io/ts-utils/typedoc/functions/isSetLike.html)(); [isStrictNullOrUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isStrictNullOrUndefined.html)(); [isStrictUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isStrictUndefined.html)(); [isString](https://nevware21.github.io/ts-utils/typedoc/functions/isString.html)(); [isThenable](https://nevware21.github.io/ts-utils/typedoc/functions/isThenable.html)(); [isTypeof](https://nevware21.github.io/ts-utils/typedoc/functions/isTypeof.html)(); [isUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isUndefined.html)(); [isWeakMap](https://nevware21.github.io/ts-utils/typedoc/functions/isWeakMap.html)(); [isWeakSet](https://nevware21.github.io/ts-utils/typedoc/functions/isWeakSet.html)(); | Value Check | [hasValue](https://nevware21.github.io/ts-utils/typedoc/functions/hasValue.html)(); [isDefined](https://nevware21.github.io/ts-utils/typedoc/functions/isDefined.html)(); [isEmpty](https://nevware21.github.io/ts-utils/typedoc/functions/isEmpty.html)(); [isNotTruthy](https://nevware21.github.io/ts-utils/typedoc/functions/isNotTruthy.html)(); [isNullOrUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isNullOrUndefined.html)(); [isStrictNullOrUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isStrictNullOrUndefined.html)(); [isStrictUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isStrictUndefined.html)(); [isTruthy](https://nevware21.github.io/ts-utils/typedoc/functions/isTruthy.html)(); [isUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isUndefined.html)(); | Value | [getValueByKey](https://nevware21.github.io/ts-utils/typedoc/functions/getValueByKey.html)(); [setValueByKey](https://nevware21.github.io/ts-utils/typedoc/functions/setValueByKey.html)(); [getValueByIter](https://nevware21.github.io/ts-utils/typedoc/functions/getValueByIter.html)(); [setValueByIter](https://nevware21.github.io/ts-utils/typedoc/functions/setValueByIter.html)(); [encodeAsJson](https://nevware21.github.io/ts-utils/typedoc/functions/encodeAsJson.html)(); [encodeAsHtml](https://nevware21.github.io/ts-utils/typedoc/functions/encodeAsHtml.html)(); [asString](https://nevware21.github.io/ts-utils/typedoc/functions/asString.html)(); [getIntValue](https://nevware21.github.io/ts-utils/typedoc/functions/getIntValue.html)(); [normalizeJsName](https://nevware21.github.io/ts-utils/typedoc/functions/normalizeJsName.html)(); |   |   -| Array | [arrAppend](https://nevware21.github.io/ts-utils/typedoc/functions/arrAppend.html)(); [arrContains](https://nevware21.github.io/ts-utils/typedoc/functions/arrContains.html)(); [arrEvery](https://nevware21.github.io/ts-utils/typedoc/functions/arrEvery.html)(); [arrFilter](https://nevware21.github.io/ts-utils/typedoc/functions/arrFilter.html)(); [arrFind](https://nevware21.github.io/ts-utils/typedoc/functions/arrFind.html)(); [arrFindIndex](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindIndex.html)(); [arrFindLast](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindLast.html)(); [arrFindLastIndex](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindLastIndex.html)(); [arrForEach](https://nevware21.github.io/ts-utils/typedoc/functions/arrForEach.html)(); [arrFrom](https://nevware21.github.io/ts-utils/typedoc/functions/arrFrom.html)(); [arrIncludes](https://nevware21.github.io/ts-utils/typedoc/functions/arrIncludes.html)(); [arrIndexOf](https://nevware21.github.io/ts-utils/typedoc/functions/arrIndexOf.html)(); [arrLastIndexOf](https://nevware21.github.io/ts-utils/typedoc/functions/arrLastIndexOf.html)(); [arrMap](https://nevware21.github.io/ts-utils/typedoc/functions/arrMap.html)(); [arrReduce](https://nevware21.github.io/ts-utils/typedoc/functions/arrReduce.html)(); [arrSlice](https://nevware21.github.io/ts-utils/typedoc/functions/arrSlice.html)(); [arrSome](https://nevware21.github.io/ts-utils/typedoc/functions/arrSome.html)(); [getLength](https://nevware21.github.io/ts-utils/typedoc/functions/getLength.html)(); [isArray](https://nevware21.github.io/ts-utils/typedoc/functions/isArray.html)();
[polyIsArray](https://nevware21.github.io/ts-utils/typedoc/functions/polyIsArray.html)(); [polyArrIncludes](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrIncludes.html)(); [polyArrFind](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFind.html)(); [polyArrFindIndex](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFindIndex.html)(); [polyArrFindLastIndex](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFindLastIndex.html)(); [polyArrFindLast](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFindLast.html)(); [polyArrFindLastIndex](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFindLastIndex.html)(); [polyArrFrom](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFrom.html)();
-| ArrayLike | [arrContains](https://nevware21.github.io/ts-utils/typedoc/functions/arrContains.html)(); [arrEvery](https://nevware21.github.io/ts-utils/typedoc/functions/arrEvery.html)(); [arrFilter](https://nevware21.github.io/ts-utils/typedoc/functions/arrFilter.html)(); [arrFind](https://nevware21.github.io/ts-utils/typedoc/functions/arrFind.html)(); [arrFindIndex](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindIndex.html)(); [arrFindLast](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindLast.html)(); [arrFindLastIndex](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindLastIndex.html)(); [arrForEach](https://nevware21.github.io/ts-utils/typedoc/functions/arrForEach.html)(); [arrFrom](https://nevware21.github.io/ts-utils/typedoc/functions/arrFrom.html)(); [arrIncludes](https://nevware21.github.io/ts-utils/typedoc/functions/arrIncludes.html)(); [arrIndexOf](https://nevware21.github.io/ts-utils/typedoc/functions/arrIndexOf.html)(); [arrLastIndexOf](https://nevware21.github.io/ts-utils/typedoc/functions/arrLastIndexOf.html)(); [arrMap](https://nevware21.github.io/ts-utils/typedoc/functions/arrMap.html)(); [arrReduce](https://nevware21.github.io/ts-utils/typedoc/functions/arrReduce.html)(); [arrSlice](https://nevware21.github.io/ts-utils/typedoc/functions/arrSlice.html)(); [arrSome](https://nevware21.github.io/ts-utils/typedoc/functions/arrSome.html)(); [getLength](https://nevware21.github.io/ts-utils/typedoc/functions/getLength.html)(); [objEntries](https://nevware21.github.io/ts-utils/typedoc/functions/objEntries.html)(); [objValues](https://nevware21.github.io/ts-utils/typedoc/functions/objValues.html)(); +| Array | [arrAppend](https://nevware21.github.io/ts-utils/typedoc/functions/arrAppend.html)(); [arrChunk](https://nevware21.github.io/ts-utils/typedoc/functions/arrChunk.html)(); [arrCompact](https://nevware21.github.io/ts-utils/typedoc/functions/arrCompact.html)(); [arrContains](https://nevware21.github.io/ts-utils/typedoc/functions/arrContains.html)(); [arrEvery](https://nevware21.github.io/ts-utils/typedoc/functions/arrEvery.html)(); [arrFilter](https://nevware21.github.io/ts-utils/typedoc/functions/arrFilter.html)(); [arrFind](https://nevware21.github.io/ts-utils/typedoc/functions/arrFind.html)(); [arrFindIndex](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindIndex.html)(); [arrFindLast](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindLast.html)(); [arrFindLastIndex](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindLastIndex.html)(); [arrFlatten](https://nevware21.github.io/ts-utils/typedoc/functions/arrFlatten.html)(); [arrForEach](https://nevware21.github.io/ts-utils/typedoc/functions/arrForEach.html)(); [arrFrom](https://nevware21.github.io/ts-utils/typedoc/functions/arrFrom.html)(); [arrGroupBy](https://nevware21.github.io/ts-utils/typedoc/functions/arrGroupBy.html)(); [arrIncludes](https://nevware21.github.io/ts-utils/typedoc/functions/arrIncludes.html)(); [arrIndexOf](https://nevware21.github.io/ts-utils/typedoc/functions/arrIndexOf.html)(); [arrLastIndexOf](https://nevware21.github.io/ts-utils/typedoc/functions/arrLastIndexOf.html)(); [arrMap](https://nevware21.github.io/ts-utils/typedoc/functions/arrMap.html)(); [arrReduce](https://nevware21.github.io/ts-utils/typedoc/functions/arrReduce.html)(); [arrSlice](https://nevware21.github.io/ts-utils/typedoc/functions/arrSlice.html)(); [arrSome](https://nevware21.github.io/ts-utils/typedoc/functions/arrSome.html)(); [arrUnique](https://nevware21.github.io/ts-utils/typedoc/functions/arrUnique.html)(); [getLength](https://nevware21.github.io/ts-utils/typedoc/functions/getLength.html)(); [isArray](https://nevware21.github.io/ts-utils/typedoc/functions/isArray.html)();
[polyIsArray](https://nevware21.github.io/ts-utils/typedoc/functions/polyIsArray.html)(); [polyArrIncludes](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrIncludes.html)(); [polyArrFind](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFind.html)(); [polyArrFindIndex](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFindIndex.html)(); [polyArrFindLastIndex](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFindLastIndex.html)(); [polyArrFindLast](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFindLast.html)(); [polyArrFindLastIndex](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFindLastIndex.html)(); [polyArrFrom](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFrom.html)();
+| ArrayLike | [arrChunk](https://nevware21.github.io/ts-utils/typedoc/functions/arrChunk.html)(); [arrCompact](https://nevware21.github.io/ts-utils/typedoc/functions/arrCompact.html)(); [arrContains](https://nevware21.github.io/ts-utils/typedoc/functions/arrContains.html)(); [arrEvery](https://nevware21.github.io/ts-utils/typedoc/functions/arrEvery.html)(); [arrFilter](https://nevware21.github.io/ts-utils/typedoc/functions/arrFilter.html)(); [arrFind](https://nevware21.github.io/ts-utils/typedoc/functions/arrFind.html)(); [arrFindIndex](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindIndex.html)(); [arrFindLast](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindLast.html)(); [arrFindLastIndex](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindLastIndex.html)(); [arrFlatten](https://nevware21.github.io/ts-utils/typedoc/functions/arrFlatten.html)(); [arrForEach](https://nevware21.github.io/ts-utils/typedoc/functions/arrForEach.html)(); [arrFrom](https://nevware21.github.io/ts-utils/typedoc/functions/arrFrom.html)(); [arrGroupBy](https://nevware21.github.io/ts-utils/typedoc/functions/arrGroupBy.html)(); [arrIncludes](https://nevware21.github.io/ts-utils/typedoc/functions/arrIncludes.html)(); [arrIndexOf](https://nevware21.github.io/ts-utils/typedoc/functions/arrIndexOf.html)(); [arrLastIndexOf](https://nevware21.github.io/ts-utils/typedoc/functions/arrLastIndexOf.html)(); [arrMap](https://nevware21.github.io/ts-utils/typedoc/functions/arrMap.html)(); [arrReduce](https://nevware21.github.io/ts-utils/typedoc/functions/arrReduce.html)(); [arrSlice](https://nevware21.github.io/ts-utils/typedoc/functions/arrSlice.html)(); [arrSome](https://nevware21.github.io/ts-utils/typedoc/functions/arrSome.html)(); [arrUnique](https://nevware21.github.io/ts-utils/typedoc/functions/arrUnique.html)(); [getLength](https://nevware21.github.io/ts-utils/typedoc/functions/getLength.html)(); [objEntries](https://nevware21.github.io/ts-utils/typedoc/functions/objEntries.html)(); [objValues](https://nevware21.github.io/ts-utils/typedoc/functions/objValues.html)(); | DOM | [isElement](https://nevware21.github.io/ts-utils/typedoc/functions/isElement.html)(); [isElementLike](https://nevware21.github.io/ts-utils/typedoc/functions/isElementLike.html)(); | Enum | [createEnum](https://nevware21.github.io/ts-utils/typedoc/functions/createEnum.html)(); [createEnumKeyMap](https://nevware21.github.io/ts-utils/typedoc/functions/createEnumKeyMap.html)(); [createEnumValueMap](https://nevware21.github.io/ts-utils/typedoc/functions/createEnumValueMap.html)(); [createSimpleMap](https://nevware21.github.io/ts-utils/typedoc/functions/createSimpleMap.html)(); [createTypeMap](https://nevware21.github.io/ts-utils/typedoc/functions/createTypeMap.html)(); | Error | [createCustomError](https://nevware21.github.io/ts-utils/typedoc/functions/createCustomError.html)(); [isError](https://nevware21.github.io/ts-utils/typedoc/functions/isError.html)(); [throwError](https://nevware21.github.io/ts-utils/typedoc/functions/throwError.html)(); [throwRangeError](https://nevware21.github.io/ts-utils/typedoc/functions/throwRangeError.html)(); [throwTypeError](https://nevware21.github.io/ts-utils/typedoc/functions/throwTypeError.html)(); [throwUnsupported](https://nevware21.github.io/ts-utils/typedoc/functions/throwUnsupported.html)(); diff --git a/lib/src/array/chunk.ts b/lib/src/array/chunk.ts new file mode 100644 index 00000000..545c3a9d --- /dev/null +++ b/lib/src/array/chunk.ts @@ -0,0 +1,56 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { isArrayLike } from "../helpers/base"; +import { arrForEach } from "./forEach"; + +/** + * The arrChunk() method returns a new array with elements divided into groups of a specified size. + * The last group may have fewer elements if the array length is not divisible by the chunk size. + * @function + * @since 0.13.0 + * @group Array + * @group ArrayLike + * @typeParam T - Identifies the base type of array elements + * @param theArray - The array or array-like object to chunk + * @param size - The size of each chunk. Must be a positive integer + * @returns A new array of chunks, where each chunk is an array of the specified size + * @example + * ```ts + * arrChunk([1, 2, 3, 4, 5, 6, 7], 2); // [[1, 2], [3, 4], [5, 6], [7]] + * arrChunk([1, 2, 3, 4, 5], 3); // [[1, 2, 3], [4, 5]] + * arrChunk([1, 2, 3], 1); // [[1], [2], [3]] + * arrChunk([1, 2, 3], 5); // [[1, 2, 3]] + * arrChunk([], 2); // [] + * + * // Array-like objects + * arrChunk({ length: 5, 0: "a", 1: "b", 2: "c", 3: "d", 4: "e" }, 2); + * // [["a", "b"], ["c", "d"], ["e"]] + * ``` + */ +/*#__NO_SIDE_EFFECTS__*/ +export function arrChunk(theArray: ArrayLike | null | undefined, size: number): T[][] { + const result: T[][] = []; + + if (isArrayLike(theArray) && size > 0) { + let idx = 0; + let chunkIdx = 0; + + arrForEach(theArray, (item) => { + if (idx % size === 0) { + result.push([]); + chunkIdx = result.length - 1; + } + + result[chunkIdx].push(item); + idx++; + }); + } + + return result; +} diff --git a/lib/src/array/compact.ts b/lib/src/array/compact.ts new file mode 100644 index 00000000..b08bfc76 --- /dev/null +++ b/lib/src/array/compact.ts @@ -0,0 +1,46 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { isArrayLike } from "../helpers/base"; +import { arrForEach } from "./forEach"; + +/** + * The arrCompact() method returns a new array with all falsy values removed. + * Falsy values include: false, 0, -0, 0n, "", null, undefined, and NaN. + * @function + * @since 0.13.0 + * @group Array + * @group ArrayLike + * @typeParam T - Identifies the base type of array elements + * @param theArray - The array or array-like object to compact + * @returns A new array with all falsy values filtered out + * @example + * ```ts + * arrCompact([0, 1, false, 2, "", 3, null, undefined, 4]); // [1, 2, 3, 4] + * arrCompact([false, 0, "", null, undefined]); // [] + * arrCompact([1, 2, 3]); // [1, 2, 3] + * arrCompact([]); // [] + * + * // Array-like objects + * arrCompact({ length: 5, 0: 0, 1: 1, 2: false, 3: 2, 4: null }); // [1, 2] + * ``` + */ +/*#__NO_SIDE_EFFECTS__*/ +export function arrCompact(theArray: ArrayLike | null | undefined): T[] { + const result: T[] = []; + + if (isArrayLike(theArray)) { + arrForEach(theArray, (item) => { + if (item) { + result.push(item); + } + }); + } + + return result; +} diff --git a/lib/src/array/flatten.ts b/lib/src/array/flatten.ts new file mode 100644 index 00000000..45084c77 --- /dev/null +++ b/lib/src/array/flatten.ts @@ -0,0 +1,67 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { isArray, isArrayLike, isUndefined } from "../helpers/base"; +import { arrForEach } from "./forEach"; + +function _addItems(result: any[], arr: any, d: number): void { + const arrLen = arr.length; + let arrIdx = 0; + while (arrIdx < arrLen) { + const item = arr[arrIdx]; + if (d > 0 && isArray(item)) { + _addItems(result, item, d - 1); + } else { + result.push(item); + } + arrIdx++; + } +} + + +/** + * The arrFlatten() method returns a new array with all sub-array elements flattened + * up to the specified depth (default 1). + * @function + * @since 0.13.0 + * @group Array + * @group ArrayLike + * @typeParam T - Identifies the base type of array elements + * @param theArray - The array or array-like object to flatten + * @param depth - The flattening depth, defaults to 1. Use Infinity for complete flattening + * @returns A new flattened array + * @example + * ```ts + * arrFlatten([1, [2, 3], [4, [5, 6]]]); // [1, 2, 3, 4, [5, 6]] + * arrFlatten([1, [2, 3], [4, [5, 6]]], 2); // [1, 2, 3, 4, 5, 6] + * arrFlatten([1, [2, 3], [4, [5, 6]]], Infinity); // [1, 2, 3, 4, 5, 6] + * arrFlatten([1, 2, 3]); // [1, 2, 3] + * arrFlatten([]); // [] + * + * // With array-like objects + * arrFlatten({ length: 2, 0: 1, 1: [2, 3] }); // [1, 2, 3] + * ``` + */ +/*#__NO_SIDE_EFFECTS__*/ +export function arrFlatten(theArray: ArrayLike | null | undefined, depth?: number): any[] { + const result: any[] = []; + + if (isArrayLike(theArray)) { + const d = isUndefined(depth) ? 1 : depth; + + arrForEach(theArray, (item) => { + if (d > 0 && isArray(item)) { + _addItems(result, item, d - 1); + } else { + result.push(item); + } + }); + } + + return result; +} diff --git a/lib/src/array/groupBy.ts b/lib/src/array/groupBy.ts new file mode 100644 index 00000000..2410cee0 --- /dev/null +++ b/lib/src/array/groupBy.ts @@ -0,0 +1,68 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { isArrayLike, isFunction } from "../helpers/base"; +import { objHasOwn } from "../object/has_own"; +import { asString } from "../string/as_string"; +import { arrForEach } from "./forEach"; + +/** + * Callback function type for arrGroupBy + * @typeParam T - Identifies the base type of array elements + */ +export type ArrGroupByCallbackFn = (value: T, index: number, array: ArrayLike) => string | number | symbol; + +/** + * The arrGroupBy() method groups array elements by the result of a callback function, + * returning an object where keys are group identifiers and values are arrays of grouped elements. + * @function + * @since 0.13.0 + * @group Array + * @group ArrayLike + * @typeParam T - Identifies the base type of array elements + * @param theArray - The array or array-like object to group + * @param callbackFn - Function that determines the group key for each element + * @param thisArg - The value to use as 'this' when executing callbackFn + * @returns An object with group keys as properties and arrays of grouped elements as values + * @example + * ```ts + * const numbers = [1, 2, 3, 4, 5, 6]; + * const grouped = arrGroupBy(numbers, (n) => n % 2 === 0 ? "even" : "odd"); + * // { odd: [1, 3, 5], even: [2, 4, 6] } + * + * const people = [ + * { name: "Alice", age: 30 }, + * { name: "Bob", age: 25 }, + * { name: "Charlie", age: 30 } + * ]; + * const byAge = arrGroupBy(people, (p) => p.age); + * // { "25": [{ name: "Bob", age: 25 }], "30": [{ name: "Alice", age: 30 }, { name: "Charlie", age: 30 }] } + * ``` + */ +/*#__NO_SIDE_EFFECTS__*/ +export function arrGroupBy( + theArray: ArrayLike | null | undefined, + callbackFn: ArrGroupByCallbackFn, + thisArg?: any +): Record { + const result: Record = {}; + + if (isArrayLike(theArray) && isFunction(callbackFn)) { + arrForEach(theArray, (item, idx) => { + const keyStr = asString(callbackFn.call(thisArg, item, idx, theArray)); + + if (!objHasOwn(result, keyStr)) { + result[keyStr] = []; + } + + result[keyStr].push(item); + }); + } + + return result; +} diff --git a/lib/src/array/unique.ts b/lib/src/array/unique.ts new file mode 100644 index 00000000..cf428aef --- /dev/null +++ b/lib/src/array/unique.ts @@ -0,0 +1,55 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { isArrayLike, isUndefined } from "../helpers/base"; +import { normalizeJsName } from "../helpers/encode"; +import { objCreate } from "../object/create"; +import { objHasOwn } from "../object/has_own"; +import { arrForEach } from "./forEach"; + +/** + * The arrUnique() method returns a new array with duplicate elements removed. + * Uses strict equality (===) for comparison and maintains insertion order. + * @function + * @since 0.13.0 + * @group Array + * @group ArrayLike + * @typeParam T - Identifies the base type of array elements + * @param theArray - The array or array-like object to remove duplicates from + * @returns A new array with duplicate values removed, preserving order of first occurrence + * @example + * ```ts + * arrUnique([1, 2, 2, 3, 1, 4]); // [1, 2, 3, 4] + * arrUnique(["a", "b", "a", "c"]); // ["a", "b", "c"] + * arrUnique([1, "1", 1, "1"]); // [1, "1"] + * arrUnique([]); // [] + * arrUnique([1]); // [1] + * + * // Array-like objects + * arrUnique({ length: 4, 0: 1, 1: 2, 2: 2, 3: 3 }); // [1, 2, 3] + * ``` + */ +/*#__NO_SIDE_EFFECTS__*/ +export function arrUnique(theArray: ArrayLike | null | undefined): T[] { + const result: T[] = []; + + if (isArrayLike(theArray)) { + const seen: any = objCreate(null); + + arrForEach(theArray, (item) => { + const key = normalizeJsName((typeof item) + "_" + item); + + if (!objHasOwn(seen, key)) { + seen[key] = 1; + result.push(item); + } + }); + } + + return result; +} diff --git a/lib/src/helpers/base.ts b/lib/src/helpers/base.ts index 09bca689..2ae0b4ef 100644 --- a/lib/src/helpers/base.ts +++ b/lib/src/helpers/base.ts @@ -6,7 +6,7 @@ * Licensed under the MIT license. */ -import { ArrCls, FUNCTION, NULL_VALUE, OBJECT, ObjProto, TO_STRING, UNDEFINED, UNDEF_VALUE } from "../internal/constants"; +import { ArrCls, FUNCTION, LENGTH, NULL_VALUE, OBJECT, ObjProto, TO_STRING, UNDEFINED, UNDEF_VALUE } from "../internal/constants"; import { _isPolyfillType } from "../internal/poly_helpers"; import { _pureRef } from "../internal/treeshake_helpers"; import { safeGet } from "./safe_get"; @@ -542,6 +542,32 @@ export function isObject(value: T): value is T { */ export const isArray: (arg: any) => arg is Array = (/* #__PURE__*/_pureRef(ArrCls as any, "isArray")); +/** + * Checks if the type of value is array-like (has a numeric length property and numeric indices). + * @since 0.13.0 + * @function + * @group Type Identity + * @group Array + * @param arg - Value to be checked. + * @return True if the value is array-like, false otherwise. + * @example + * ```ts + * import { isArrayLike } from "@nevware21/ts-utils"; + * + * isArrayLike([1, 2, 3]); // true + * isArrayLike("hello"); // true + * isArrayLike({ length: 3, 0: "a", 1: "b", 2: "c" }); // true + * isArrayLike({ length: "3" }); // false (length is not a number) + * isArrayLike({ 0: "a", 1: "b" }); // false (no length property) + * isArrayLike(null); // false + * isArrayLike(undefined); // false + * ``` + */ +/*#__NO_SIDE_EFFECTS__*/ +export function isArrayLike(arg: any): arg is ArrayLike { + return !isStrictNullOrUndefined(arg) && !isFunction(arg) && isNumber(arg[LENGTH]) && arg[LENGTH] >= 0; +} + /** * Check if an object is of type Date * @function diff --git a/lib/src/helpers/customError.ts b/lib/src/helpers/customError.ts index 9e710c29..b9ef350f 100644 --- a/lib/src/helpers/customError.ts +++ b/lib/src/helpers/customError.ts @@ -55,7 +55,7 @@ function _setName(baseClass: any, name: string) { * @param constructCb - [Optional] An optional callback function to call when a * new Custom Error instance is being created. * @param errorBase - [Optional] (since v0.9.6) The error class to extend for this class, defaults to Error. - * @param superArgsFn - [Optional] (since v0.12.7) An optional function that receives the constructor arguments and + * @param superArgsFn - [Optional] (since v0.13.0) An optional function that receives the constructor arguments and * returns the arguments to pass to the base class constructor. When not provided all constructor * arguments are forwarded to the base class. Use this to support a different argument order or * to pass a subset of arguments to the base class (similar to calling `super(...)` in a class). diff --git a/lib/src/index.ts b/lib/src/index.ts index 3c0dcdf1..fb35ead9 100644 --- a/lib/src/index.ts +++ b/lib/src/index.ts @@ -8,22 +8,27 @@ export { arrAppend } from "./array/append"; export { ArrPredicateCallbackFn, ArrPredicateCallbackFn2, ArrMapCallbackFn, ArrFromMapFn } from "./array/callbacks"; +export { arrChunk } from "./array/chunk"; +export { arrCompact } from "./array/compact"; export { arrEvery, arrFilter } from "./array/every"; export { arrFind, arrFindIndex, arrFindLast, arrFindLastIndex } from "./array/find"; +export { arrFlatten } from "./array/flatten"; export { arrForEach } from "./array/forEach"; export { arrFrom } from "./array/from"; +export { ArrGroupByCallbackFn, arrGroupBy } from "./array/groupBy"; export { arrContains, arrIncludes } from "./array/includes"; export { arrIndexOf, arrLastIndexOf } from "./array/indexOf"; export { arrMap } from "./array/map"; export { ArrReduceCallbackFn, arrReduce } from "./array/reduce"; export { arrSlice } from "./array/slice"; export { arrSome } from "./array/some"; +export { arrUnique } from "./array/unique"; export { fnApply, fnBind, fnCall } from "./funcs/funcs"; export { createFnDeferredProxy, createProxyFuncs } from "./funcs/fnProxy"; export { readArgs } from "./funcs/readArgs"; export { ProxyFunctionDef, TypeFuncNames } from "./funcs/types"; export { - isTypeof, isUndefined, isNullOrUndefined, isDefined, isString, isFunction, isObject, isArray, isDate, isNumber, isBoolean, + isTypeof, isUndefined, isNullOrUndefined, isDefined, isString, isFunction, isObject, isArray, isArrayLike, isDate, isNumber, isBoolean, isRegExp, isFile, isFormData, isBlob, isArrayBuffer, isPromiseLike, isPromise, isThenable, isNotTruthy, isTruthy, objToString, isStrictNullOrUndefined, isStrictUndefined, isError, isPrimitive, isPrimitiveType, isMap, isMapLike, isSet, isSetLike, isWeakMap, isWeakSet, isBigInt, isAsyncFunction, isGenerator, isAsyncGenerator diff --git a/lib/src/internal/unwrapFunction.ts b/lib/src/internal/unwrapFunction.ts index 1c2ab8d6..1a09072b 100644 --- a/lib/src/internal/unwrapFunction.ts +++ b/lib/src/internal/unwrapFunction.ts @@ -22,7 +22,7 @@ import { ArrSlice, CALL, NULL_VALUE } from "./constants"; * @param funcName - The function name to call on the first argument passed to the wrapped function * @returns A function which will call the funcName against the first passed argument and pass on the remaining arguments */ -export const _unwrapInstFunction:(funcName: keyof T) => (this: T, ..._args:any) => R = (/*#__PURE__*/_unwrapFunctionWithPoly); +export const _unwrapInstFunction:(funcName: keyof T) => (this: T, ..._args:any) => R = _unwrapFunctionWithPoly; /** * @function @@ -33,7 +33,7 @@ export const _unwrapInstFunction:(funcName: keyof T) => (this: T, ..._a * @param clsProto - The Class or class prototype to fallback to if the instance doesn't have the function. * @returns A function which will call the funcName against the first passed argument and pass on the remaining arguments */ -export const _unwrapFunction:(funcName: keyof T, clsProto: T) => (this: T, ..._args:any) => R = (/*#__PURE__*/_unwrapFunctionWithPoly); +export const _unwrapFunction:(funcName: keyof T, clsProto: T) => (this: T, ..._args:any) => R = _unwrapFunctionWithPoly; /** * @internal diff --git a/lib/test/bundle-size-check.js b/lib/test/bundle-size-check.js index 1ea01258..326e594a 100644 --- a/lib/test/bundle-size-check.js +++ b/lib/test/bundle-size-check.js @@ -7,13 +7,13 @@ const configs = [ { name: "es5-min-full", path: "../bundle/es5/umd/ts-utils.min.js", - limit: 25.5 * 1024, // 25.5 kb in bytes + limit: 26.5 * 1024, // 26.5 kb in bytes compress: false }, { name: "es6-min-full", path: "../bundle/es6/umd/ts-utils.min.js", - limit: 25 * 1024, // 25 kb in bytes + limit: 26 * 1024, // 26 kb in bytes compress: false }, { diff --git a/lib/test/src/common/helpers/array.test.ts b/lib/test/src/common/helpers/array.test.ts index 489f107e..707bf9a0 100644 --- a/lib/test/src/common/helpers/array.test.ts +++ b/lib/test/src/common/helpers/array.test.ts @@ -8,8 +8,12 @@ import { assert } from "@nevware21/tripwire-chai"; import { arrAppend } from "../../../../src/array/append"; +import { arrChunk } from "../../../../src/array/chunk"; +import { arrCompact } from "../../../../src/array/compact"; import { arrFind, arrFindIndex, arrFindLast, arrFindLastIndex } from "../../../../src/array/find"; +import { arrFlatten } from "../../../../src/array/flatten"; import { arrFrom } from "../../../../src/array/from"; +import { arrGroupBy } from "../../../../src/array/groupBy"; import { arrSome } from "../../../../src/array/some"; import { arrForEach } from "../../../../src/array/forEach"; import { arrContains, arrIncludes } from "../../../../src/array/includes"; @@ -18,6 +22,7 @@ import { arrEvery, arrFilter } from "../../../../src/array/every"; import { arrMap } from "../../../../src/array/map"; import { arrReduce } from "../../../../src/array/reduce"; import { arrSlice } from "../../../../src/array/slice"; +import { arrUnique } from "../../../../src/array/unique"; import { dumpObj } from "../../../../src/helpers/diagnostics"; describe("array helpers", () => { @@ -973,6 +978,202 @@ describe("array helpers", () => { }); }); + describe("arrUnique", () => { + it("removes duplicate primitives", () => { + assert.deepEqual(arrUnique([1, 2, 2, 3, 1, 4]), [1, 2, 3, 4]); + assert.deepEqual(arrUnique(["a", "b", "a", "c"]), ["a", "b", "c"]); + assert.deepEqual(arrUnique([1, "1", 1, "1"]), [1, "1"]); + }); + + it("preserves insertion order", () => { + assert.deepEqual(arrUnique([3, 1, 2, 1, 3]), [3, 1, 2]); + assert.deepEqual(arrUnique(["z", "a", "z", "b"]), ["z", "a", "b"]); + }); + + it("handles empty and single element arrays", () => { + assert.deepEqual(arrUnique([]), []); + assert.deepEqual(arrUnique([1]), [1]); + }); + + it("handles null and undefined", () => { + assert.deepEqual(arrUnique(null), []); + assert.deepEqual(arrUnique(undefined), []); + }); + + it("handles array-like objects", () => { + assert.deepEqual(arrUnique({ length: 4, 0: 1, 1: 2, 2: 2, 3: 3 }), [1, 2, 3]); + assert.deepEqual(arrUnique({ length: 3, 0: "a", 1: "b", 2: "a" }), ["a", "b"]); + }); + + it("uses strict equality", () => { + assert.deepEqual(arrUnique([0, false, 0, false]), [0, false]); + assert.deepEqual(arrUnique([null, undefined, null]), [null, undefined]); + }); + }); + + describe("arrCompact", () => { + it("removes falsy values", () => { + assert.deepEqual(arrCompact([0, 1, false, 2, "", 3, null, undefined, 4]), [1, 2, 3, 4]); + assert.deepEqual(arrCompact([false, 0, "", null, undefined]), []); + }); + + it("preserves truthy values", () => { + assert.deepEqual(arrCompact([1, 2, 3]), [1, 2, 3]); + assert.deepEqual(arrCompact(["a", "b", "c"]), ["a", "b", "c"]); + assert.deepEqual(arrCompact([true, true, true]), [true, true, true]); + }); + + it("handles empty arrays", () => { + assert.deepEqual(arrCompact([]), []); + }); + + it("handles null and undefined", () => { + assert.deepEqual(arrCompact(null), []); + assert.deepEqual(arrCompact(undefined), []); + }); + + it("handles array-like objects", () => { + assert.deepEqual(arrCompact({ length: 5, 0: 0, 1: 1, 2: false, 3: 2, 4: null }), [1, 2]); + assert.deepEqual(arrCompact({ length: 3, 0: "a", 1: "", 2: "b" }), ["a", "b"]); + }); + + it("preserves numbers and strings properly", () => { + assert.deepEqual(arrCompact([1, 0, 2]), [1, 2]); + assert.deepEqual(arrCompact(["hello", "", "world"]), ["hello", "world"]); + }); + }); + + describe("arrFlatten", () => { + it("flattens with default depth of 1", () => { + assert.deepEqual(arrFlatten([1, [2, 3], [4, [5, 6]]]), [1, 2, 3, 4, [5, 6]]); + assert.deepEqual(arrFlatten([1, [2], 3]), [1, 2, 3]); + }); + + it("flattens with specified depth", () => { + assert.deepEqual(arrFlatten([1, [2, 3], [4, [5, 6]]], 2), [1, 2, 3, 4, 5, 6]); + assert.deepEqual(arrFlatten([1, [2, [3, [4]]]], 2), [1, 2, 3, [4]]); + }); + + it("flattens with infinite depth", () => { + assert.deepEqual(arrFlatten([1, [2, 3], [4, [5, 6]]], Infinity), [1, 2, 3, 4, 5, 6]); + assert.deepEqual(arrFlatten([1, [2, [3, [4, [5]]]]], Infinity), [1, 2, 3, 4, 5]); + }); + + it("handles non-arrays when depth is 0", () => { + assert.deepEqual(arrFlatten([1, [2, 3]], 0), [1, [2, 3]]); + }); + + it("preserves non-nested arrays", () => { + assert.deepEqual(arrFlatten([1, 2, 3]), [1, 2, 3]); + }); + + it("handles empty arrays", () => { + assert.deepEqual(arrFlatten([]), []); + }); + + it("handles null and undefined", () => { + assert.deepEqual(arrFlatten(null), []); + assert.deepEqual(arrFlatten(undefined), []); + }); + + it("handles array-like objects", () => { + assert.deepEqual(arrFlatten({ length: 2, 0: 1, 1: [2, 3] }), [1, 2, 3]); + }); + }); + + describe("arrGroupBy", () => { + it("groups by simple criteria", () => { + const numbers = [1, 2, 3, 4, 5, 6]; + const grouped = arrGroupBy(numbers, (n) => n % 2 === 0 ? "even" : "odd"); + assert.deepEqual(grouped["odd"], [1, 3, 5]); + assert.deepEqual(grouped["even"], [2, 4, 6]); + }); + + it("groups objects by property", () => { + const people = [ + { name: "Alice", age: 30 }, + { name: "Bob", age: 25 }, + { name: "Charlie", age: 30 } + ]; + const byAge = arrGroupBy(people, (p) => p.age); + assert.equal(byAge["25"].length, 1); + assert.equal(byAge["30"].length, 2); + assert.equal(byAge["25"][0].name, "Bob"); + }); + + it("handles empty arrays", () => { + const grouped = arrGroupBy([], (x) => x); + assert.deepEqual(grouped, {}); + }); + + it("handles null and undefined input", () => { + assert.deepEqual(arrGroupBy(null as any, (x: any) => x), {}); + assert.deepEqual(arrGroupBy(undefined as any, (x: any) => x), {}); + }); + + it("handles null callback", () => { + assert.deepEqual(arrGroupBy([1, 2, 3], null as any), {}); + }); + + it("supports thisArg parameter", () => { + const context = { multiplier: 2 }; + const numbers = [1, 2, 3, 4]; + const grouped = arrGroupBy(numbers, function(n) { + return (n * (this as any).multiplier) % 2 === 0 ? "even" : "odd"; + }, context); + assert.ok(grouped["odd"] || grouped["even"]); + }); + + it("handles array-like objects", () => { + const arrayLike = { length: 4, 0: 1, 1: 2, 2: 3, 3: 4 }; + const grouped = arrGroupBy(arrayLike, (n) => n % 2 === 0 ? "even" : "odd"); + assert.equal(grouped["odd"].length, 2); + assert.equal(grouped["even"].length, 2); + }); + }); + + describe("arrChunk", () => { + it("chunks array into specified size", () => { + assert.deepEqual(arrChunk([1, 2, 3, 4, 5, 6, 7], 2), [[1, 2], [3, 4], [5, 6], [7]]); + assert.deepEqual(arrChunk([1, 2, 3, 4, 5], 3), [[1, 2, 3], [4, 5]]); + }); + + it("handles chunk size of 1", () => { + assert.deepEqual(arrChunk([1, 2, 3], 1), [[1], [2], [3]]); + }); + + it("handles chunk size larger than array", () => { + assert.deepEqual(arrChunk([1, 2, 3], 5), [[1, 2, 3]]); + }); + + it("handles empty arrays", () => { + assert.deepEqual(arrChunk([], 2), []); + }); + + it("handles null and undefined", () => { + assert.deepEqual(arrChunk(null, 2), []); + assert.deepEqual(arrChunk(undefined, 2), []); + }); + + it("handles invalid chunk size", () => { + assert.deepEqual(arrChunk([1, 2, 3], 0), []); + assert.deepEqual(arrChunk([1, 2, 3], -1), []); + }); + + it("handles array-like objects", () => { + const arrayLike = { length: 5, 0: "a", 1: "b", 2: "c", 3: "d", 4: "e" }; + assert.deepEqual(arrChunk(arrayLike, 2), [["a", "b"], ["c", "d"], ["e"]]); + }); + + it("preserves exact chunk boundaries", () => { + const data = [10, 20, 30, 40, 50, 60]; + const chunks = arrChunk(data, 3); + assert.equal(chunks.length, 2); + assert.deepEqual(chunks[0], [10, 20, 30]); + assert.deepEqual(chunks[1], [40, 50, 60]); + }); + }); + function _expectThrow(cb: () => void): Error { try { cb(); diff --git a/lib/test/src/common/helpers/base.test.ts b/lib/test/src/common/helpers/base.test.ts index 3a330331..fcf94efc 100644 --- a/lib/test/src/common/helpers/base.test.ts +++ b/lib/test/src/common/helpers/base.test.ts @@ -8,7 +8,7 @@ import { assert } from "@nevware21/tripwire-chai"; import { - isArray, isBoolean, isDate, isDefined, isFunction, isNullOrUndefined, isNumber, isObject, isString, isTypeof, + isArray, isArrayLike, isBoolean, isDate, isDefined, isFunction, isNullOrUndefined, isNumber, isObject, isString, isTypeof, isUndefined, isRegExp, isFile, isFormData, isBlob, isArrayBuffer, isError, isPromiseLike, isPromise, isNotTruthy, isTruthy, isStrictUndefined, isStrictNullOrUndefined, isPrimitive, isPrimitiveType, isMap } from "../../../../src/helpers/base"; @@ -639,6 +639,75 @@ describe("base helpers", () => { }); }); + describe("isArrayLike", () => { + it("Validate values", () => { + assert.equal(isArrayLike(null), false, "Checking null"); + assert.equal(isArrayLike(undefined), false, "Checking undefined"); + assert.equal(isArrayLike("null"), true, "Checking 'null' (strings are array-like)"); + assert.equal(isArrayLike("undefined"), true, "Checking 'undefined' (strings are array-like)"); + assert.equal(isArrayLike("hello"), true, "Checking 'hello' (strings are array-like)"); + assert.equal(isArrayLike(""), true, "Checking '' (empty string is array-like)"); + assert.equal(isArrayLike(new Date()), false, "Checking Date"); + assert.equal(isArrayLike(1), false, "Checking 1"); + assert.equal(isArrayLike(0), false, "Checking 0"); + assert.equal(isArrayLike(_dummyFunction), false, "Checking _dummyFunction"); + assert.equal(isArrayLike([]), true, "Checking []"); + assert.equal(isArrayLike([1, 2, 3]), true, "Checking [1, 2, 3]"); + assert.equal(isArrayLike(new Array(1)), true, "Checking new Array(1)"); + assert.equal(isArrayLike(true), false, "Checking true"); + assert.equal(isArrayLike(false), false, "Checking false"); + assert.equal(isArrayLike("true"), true, "Checking 'true'"); + assert.equal(isArrayLike("false"), true, "Checking 'false'"); + assert.equal(isArrayLike(new Boolean(true)), false, "Checking new Boolean(true)"); + assert.equal(isArrayLike(new Boolean(false)), false, "Checking new Boolean(false)"); + assert.equal(isArrayLike(new Boolean("true")), false, "Checking new Boolean('true')"); + assert.equal(isArrayLike(new Boolean("false")), false, "Checking new Boolean('false')"); + assert.equal(isArrayLike(/[a-z]/g), false, "Checking '/[a-z]/g'"); + assert.equal(isArrayLike(new RegExp("")), false, "Checking new RegExp('')"); + _isFileCheck(isArrayLike, false); + _isFormDataCheck(isArrayLike, false); + _isBlobCheck(isArrayLike, false); + assert.equal(isArrayLike(new ArrayBuffer(0)), false, "Checking new ArrayBuffer([])"); + assert.equal(isArrayLike(new Error("Test Error")), false, "Checking new Error('')"); + assert.equal(isArrayLike(new TypeError("Test TypeError")), false, "Checking new TypeError('')"); + assert.equal(isArrayLike(new TestError("Test TestError")), false, "Checking new TestError('')"); + assert.equal(isArrayLike(_dummyError()), false, "Checking dummy error (object that looks like an error)"); + assert.equal(isArrayLike(Promise.reject()), false, "Checking Promise.reject"); + assert.equal(isArrayLike(Promise.resolve()), false, "Checking Promise.resolve"); + assert.equal(isArrayLike(new Promise(() => {})), false, "Checking new Promise(() => {})"); + assert.equal(isArrayLike(_simplePromise()), false, "Checking _simplePromise"); + assert.equal(isArrayLike(_simplePromiseLike()), false, "Checking _simplePromiseLike"); + assert.equal(isArrayLike(Object.create(null)), false, "Checking Object.create(null)"); + assert.equal(isArrayLike(polyObjCreate(null)), false, "Checking polyObjCreate(null)"); + }); + + it("Validate array-like objects", () => { + // Object with length property + const arrayLike = { length: 3, 0: "a", 1: "b", 2: "c" }; + assert.equal(isArrayLike(arrayLike), true, "Checking array-like object"); + + // Object with length but no numeric indices + const hasLength = { length: 5 }; + assert.equal(isArrayLike(hasLength), true, "Checking object with length property"); + + // Object with length but it's not a number + const lengthNotNumber = { length: "3" }; + assert.equal(isArrayLike(lengthNotNumber), false, "Checking object with non-numeric length"); + + // Object with no length property + const noLength = { 0: "a", 1: "b" }; + assert.equal(isArrayLike(noLength), false, "Checking object with numeric indices but no length"); + + // Object with negative length + const negativeLength = { length: -1 }; + assert.equal(isArrayLike(negativeLength), false, "Checking object with negative length"); + + // Object with length 0 + const emptyLength = { length: 0 }; + assert.equal(isArrayLike(emptyLength), true, "Checking object with length 0"); + }); + }); + describe("isDate", () => { it("Validate values", () => { assert.equal(isDate(null), false, "Checking null");