utilitish 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.txt ADDED
@@ -0,0 +1,25 @@
1
+ GNU GENERAL PUBLIC LICENSE
2
+ Version 3, 29 June 2007
3
+
4
+ Copyright (C) 2025 Donovan Ferreira
5
+
6
+ Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
7
+
8
+ Preamble
9
+
10
+ The GNU General Public License is a free, copyleft license for
11
+ software and other kinds of works.
12
+
13
+ The licenses for most software and other practical works are designed
14
+ to take away your freedom to share and change the works. By contrast,
15
+ the GNU General Public License is intended to guarantee your freedom to
16
+ share and change all versions of a program--to make sure it remains free
17
+ software for all its users. We, the Free Software Foundation, use the
18
+ GNU General Public License for most of our software; it applies also to
19
+ any other work released this way by its authors. You can apply it to
20
+ your programs, too.
21
+
22
+ [... contenu tronqué pour l’aperçu ...]
23
+
24
+ You should have received a copy of the GNU General Public License
25
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
package/README.md ADDED
@@ -0,0 +1,5 @@
1
+ ## Licence
2
+
3
+ Ce projet est distribué sous licence [GPL v3](https://www.gnu.org/licenses/gpl-3.0.html) © 2025 Donovan Ferreira.
4
+
5
+ Ceci est une petite librairie qui ajoute plusieurs méthodes au prototype JavaScript.
@@ -0,0 +1,26 @@
1
+ export {};
2
+ declare global {
3
+ interface ArrayConstructor {
4
+ /**
5
+ * Combines multiple arrays element-wise, like Python's itertools.zip_longest .
6
+ * @param arrays Arrays to zip together
7
+ * @returns An array of arrays, each containing the i-th elements of the input arrays, undefined for the shortest arrays
8
+ */
9
+ zip(...arrays: any[][]): any[][];
10
+ /**
11
+ * Generates a sequence of numbers, similar to Python's range.
12
+ * @param start First number, or length if end is not defined
13
+ * @param end Last number (excluded)
14
+ * @param step Step of the sequence (positive or negative)
15
+ * @returns An array of numbers
16
+ */
17
+ range(start: number, end?: number, step?: number): number[];
18
+ /**
19
+ * Creates an array filled with the same value or a generated value.
20
+ * @param length Length of the array
21
+ * @param value Value or function generating a value
22
+ * @returns Filled array
23
+ */
24
+ repeat<T>(length: number, value: T | (() => T)): T[];
25
+ }
26
+ }
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ Array.zip = function (...arrays) {
4
+ if (arrays.length === 0)
5
+ return [];
6
+ const maxLen = Math.max(...arrays.map((arr) => arr.length));
7
+ const result = [];
8
+ for (let i = 0; i < maxLen; i++) {
9
+ result.push(arrays.map((arr) => arr[i]));
10
+ }
11
+ return result;
12
+ };
13
+ Array.range = function (start, end, step = 1) {
14
+ if (end === undefined) {
15
+ end = start;
16
+ start = 0;
17
+ }
18
+ const result = [];
19
+ if (step === 0)
20
+ throw new Error('step must not be 0');
21
+ const condition = step > 0 ? (i) => i < end : (i) => i > end;
22
+ for (let i = start; condition(i); i += step) {
23
+ result.push(i);
24
+ }
25
+ return result;
26
+ };
27
+ Array.repeat = function (length, value) {
28
+ if (typeof length !== 'number' || !Number.isInteger(length) || length < 0) {
29
+ throw new TypeError('Indices must be integers');
30
+ }
31
+ const result = [];
32
+ for (let i = 0; i < length; i++) {
33
+ result.push(typeof value === 'function' ? value() : value);
34
+ }
35
+ return result;
36
+ };
@@ -0,0 +1 @@
1
+ import './array-constructor';
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ require("./array-constructor");
4
+ describe('Array Constructor extensions', () => {
5
+ describe('range', () => {
6
+ it('should generate a range from 0 to n-1 if only start is given', () => {
7
+ expect(Array.range(5)).toEqual([0, 1, 2, 3, 4]);
8
+ });
9
+ it('should generate a range from start to end-1', () => {
10
+ expect(Array.range(2, 6)).toEqual([2, 3, 4, 5]);
11
+ });
12
+ it('should support negative steps', () => {
13
+ expect(Array.range(5, 1, -1)).toEqual([5, 4, 3, 2]);
14
+ });
15
+ it('should throw if step is 0', () => {
16
+ expect(() => Array.range(0, 5, 0)).toThrowError('step must not be 0');
17
+ });
18
+ it('should return an empty array if start equals end', () => {
19
+ expect(Array.range(3, 3)).toEqual([]);
20
+ });
21
+ it('should return an empty array if step does not reach end', () => {
22
+ expect(Array.range(0, 5, -1)).toEqual([]);
23
+ expect(Array.range(5, 0, 1)).toEqual([]);
24
+ });
25
+ });
26
+ describe('repeat', () => {
27
+ it('should fill an array with the same value', () => {
28
+ expect(Array.repeat(3, 'a')).toEqual(['a', 'a', 'a']);
29
+ });
30
+ it('should fill an array with values from a function', () => {
31
+ let n = 0;
32
+ expect(Array.repeat(4, () => ++n)).toEqual([1, 2, 3, 4]);
33
+ });
34
+ it('should return an empty array if length is 0', () => {
35
+ expect(Array.repeat(0, 'x')).toEqual([]);
36
+ });
37
+ it('should throw if length is negative or not an integer', () => {
38
+ expect(() => Array.repeat(-1, 'a')).toThrowError();
39
+ expect(() => Array.repeat(1.5, 'a')).toThrowError();
40
+ expect(() => Array.repeat('a', 'a')).toThrowError();
41
+ });
42
+ });
43
+ describe('zip', () => {
44
+ it('should zip two arrays of equal length', () => {
45
+ expect(Array.zip([1, 2, 3], ['a', 'b', 'c'])).toEqual([
46
+ [1, 'a'],
47
+ [2, 'b'],
48
+ [3, 'c'],
49
+ ]);
50
+ });
51
+ it('should zip arrays of different lengths (fill with undefined)', () => {
52
+ expect(Array.zip([1, 2], ['a', 'b', 'c'], [true, false, true])).toEqual([
53
+ [1, 'a', true],
54
+ [2, 'b', false],
55
+ [undefined, 'c', true],
56
+ ]);
57
+ });
58
+ it('should return an empty array if no arrays are given', () => {
59
+ expect(Array.zip()).toEqual([]);
60
+ });
61
+ it('should return an array of undefineds if all arrays are empty', () => {
62
+ expect(Array.zip([], [])).toEqual([]);
63
+ });
64
+ it('should work with a single array', () => {
65
+ expect(Array.zip([1, 2, 3])).toEqual([[1], [2], [3]]);
66
+ });
67
+ it('should fill with undefined for missing elements', () => {
68
+ expect(Array.zip([1], [2, 3, 4])).toEqual([
69
+ [1, 2],
70
+ [undefined, 3],
71
+ [undefined, 4],
72
+ ]);
73
+ });
74
+ });
75
+ });
@@ -0,0 +1,68 @@
1
+ export {};
2
+ declare global {
3
+ interface Array<T> {
4
+ /**
5
+ * Returns the first element of the array, or `undefined` if the array is empty.
6
+ */
7
+ first(): T | undefined;
8
+ /**
9
+ * Returns the last element of the array, or `undefined` if the array is empty.
10
+ */
11
+ last(): T | undefined;
12
+ /**
13
+ * Calculates the sum of the array values.
14
+ * - If the array is of type `number[]`, no callback is required.
15
+ * - Otherwise, a callback must be provided to extract a numeric value.
16
+ *
17
+ * @param callback - Optional function that returns a `number` from an item.
18
+ * @throws {Error} If no callback is provided for a non-number array.
19
+ * @returns The sum of the values.
20
+ */
21
+ sum(this: number[]): number;
22
+ sum(this: T[], callback: (item: T) => number): number;
23
+ /**
24
+ * Returns a new array with only unique elements (based on strict equality).
25
+ */
26
+ unique(): T[];
27
+ /**
28
+ * Splits the array into chunks of the given size.
29
+ * @param size - Maximum size of each chunk.
30
+ */
31
+ chunk(size: number): T[][];
32
+ /**
33
+ * Calculates the average of the array values.
34
+ * - If the array is of type `number[]`, no callback is required.
35
+ * - Otherwise, a callback must be provided to extract a numeric value.
36
+ *
37
+ * @param callback - Optional function that returns a `number` from an item.
38
+ * @throws {Error} If no callback is provided for a non-number array.
39
+ * @returns The average of the values.
40
+ */
41
+ average(this: number[]): number;
42
+ average(this: T[], callback: (item: T) => number): number;
43
+ /**
44
+ * Groups the array elements based on a key returned by the callback function.
45
+ * @param callback - Function that returns a key to group by.
46
+ * @returns An object where keys are the group names and values are arrays of grouped items.
47
+ */
48
+ groupBy(callback: (item: T) => string | number): Record<string | number, T[]>;
49
+ /**
50
+ * Removes all falsy values (`false`, `null`, `0`, `""`, `undefined`, and `NaN`) from the array.
51
+ * @returns A new array with all falsy values removed.
52
+ */
53
+ compact(): T[];
54
+ /**
55
+ * Enumerates the array into tuples [value, index].
56
+ * Similar to Python's enumerate but returns value first.
57
+ * @returns An array of [value, index] pairs.
58
+ */
59
+ enumerate(): [T, number][];
60
+ /**
61
+ * Swaps the values at two indices in the array.
62
+ * @param i First index
63
+ * @param j Second index
64
+ * @returns The array itself after swapping
65
+ */
66
+ swap(i: number, j: number): this;
67
+ }
68
+ }
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const utils_1 = require("../utils");
4
+ (0, utils_1.defineIfNotExists)(Array.prototype, 'first', function () {
5
+ return this[0];
6
+ });
7
+ (0, utils_1.defineIfNotExists)(Array.prototype, 'last', function () {
8
+ return this[this.length - 1];
9
+ });
10
+ (0, utils_1.defineIfNotExists)(Array.prototype, 'sum', function (callback) {
11
+ if (typeof callback === 'function') {
12
+ return this.reduce((acc, item) => acc + callback(item), 0);
13
+ }
14
+ if (this.every((item) => typeof item === 'number')) {
15
+ return this.reduce((acc, item) => acc + item, 0);
16
+ }
17
+ throw new Error('Array.prototype.sum() requires a callback unless array is number[]');
18
+ });
19
+ (0, utils_1.defineIfNotExists)(Array.prototype, 'unique', function () {
20
+ return [...new Set(this)];
21
+ });
22
+ (0, utils_1.defineIfNotExists)(Array.prototype, 'chunk', function (size) {
23
+ if (typeof size !== 'number' || !Number.isInteger(size) || size <= 0) {
24
+ throw new TypeError('Chunk size must be a positive integer');
25
+ }
26
+ const result = [];
27
+ for (let i = 0; i < this.length; i += size) {
28
+ result.push(this.slice(i, i + size));
29
+ }
30
+ return result;
31
+ });
32
+ (0, utils_1.defineIfNotExists)(Array.prototype, 'average', function (callback) {
33
+ if (this.length === 0)
34
+ return 0;
35
+ if (typeof callback === 'function') {
36
+ return this.reduce((acc, item) => acc + callback(item), 0) / this.length;
37
+ }
38
+ if (this.every((item) => typeof item === 'number')) {
39
+ return (this.reduce((acc, item) => acc + item, 0) / this.length);
40
+ }
41
+ throw new Error('Array.prototype.average() requires a callback unless array is number[]');
42
+ });
43
+ (0, utils_1.defineIfNotExists)(Array.prototype, 'groupBy', function (callback) {
44
+ if (typeof callback !== 'function') {
45
+ throw new TypeError('Callback must be a function');
46
+ }
47
+ const result = {};
48
+ this.forEach((item) => {
49
+ const key = callback(item);
50
+ if (typeof key !== 'string' && typeof key !== 'number') {
51
+ throw new TypeError('groupBy callback must return a string or number');
52
+ }
53
+ if (!result[key])
54
+ result[key] = [];
55
+ result[key].push(item);
56
+ });
57
+ return result;
58
+ });
59
+ (0, utils_1.defineIfNotExists)(Array.prototype, 'compact', function () {
60
+ return this.filter(Boolean);
61
+ });
62
+ (0, utils_1.defineIfNotExists)(Array.prototype, 'enumerate', function () {
63
+ return this.map((value, index) => [value, index]);
64
+ });
65
+ (0, utils_1.defineIfNotExists)(Array.prototype, 'swap', function (i, j) {
66
+ if (typeof i !== 'number' ||
67
+ typeof j !== 'number' ||
68
+ !Number.isInteger(i) ||
69
+ !Number.isInteger(j)) {
70
+ throw new TypeError('Indices must be integers');
71
+ }
72
+ if (i < 0 || i >= this.length || j < 0 || j >= this.length) {
73
+ throw new RangeError('Index out of bounds');
74
+ }
75
+ if (i !== j) {
76
+ const temp = this[i];
77
+ this[i] = this[j];
78
+ this[j] = temp;
79
+ }
80
+ return this;
81
+ });
@@ -0,0 +1 @@
1
+ import './array-prototype';
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ require("./array-prototype");
4
+ describe('Array prototype extensions', () => {
5
+ describe('first', () => {
6
+ it('should return the first element or undefined', () => {
7
+ expect([1, 2, 3].first()).toBe(1);
8
+ expect([].first()).toBeUndefined();
9
+ });
10
+ });
11
+ describe('last', () => {
12
+ it('should return the last element or undefined', () => {
13
+ expect([1, 2, 3].last()).toBe(3);
14
+ expect([].last()).toBeUndefined();
15
+ });
16
+ });
17
+ describe('sum', () => {
18
+ it('should work for number[] without callback', () => {
19
+ expect([1, 2, 3].sum()).toBe(6);
20
+ });
21
+ it('should work with callback', () => {
22
+ const items = [{ x: 1 }, { x: 2 }];
23
+ expect(items.sum((item) => item.x)).toBe(3);
24
+ });
25
+ // Ajoute ici les tests d’erreur si besoin
26
+ });
27
+ describe('average', () => {
28
+ it('should work for number[] without callback', () => {
29
+ expect([2, 4, 6].average()).toBe(4);
30
+ });
31
+ it('should work with callback', () => {
32
+ const items = [{ x: 2 }, { x: 4 }];
33
+ expect(items.average((item) => item.x)).toBe(3);
34
+ });
35
+ it('should return 0 for empty array', () => {
36
+ expect([].average()).toBe(0);
37
+ });
38
+ });
39
+ describe('unique', () => {
40
+ it('should return array with unique values', () => {
41
+ expect([1, 1, 2, 2, 3].unique()).toEqual([1, 2, 3]);
42
+ });
43
+ });
44
+ describe('chunk', () => {
45
+ it('should split array into chunks', () => {
46
+ expect([1, 2, 3, 4].chunk(2)).toEqual([
47
+ [1, 2],
48
+ [3, 4],
49
+ ]);
50
+ expect([1, 2, 3].chunk(2)).toEqual([[1, 2], [3]]);
51
+ });
52
+ it('should throw if size is not a positive integer', () => {
53
+ expect(() => [1, 2, 3].chunk(0)).toThrowError(TypeError);
54
+ expect(() => [1, 2, 3].chunk(-1)).toThrowError(TypeError);
55
+ expect(() => [1, 2, 3].chunk(1.5)).toThrowError(TypeError);
56
+ expect(() => [1, 2, 3].chunk('a')).toThrowError(TypeError);
57
+ });
58
+ });
59
+ describe('groupBy', () => {
60
+ it('should group items by callback result', () => {
61
+ const items = ['a', 'ab', 'abc'];
62
+ const result = items.groupBy((item) => item.length);
63
+ expect(result).toEqual({ 1: ['a'], 2: ['ab'], 3: ['abc'] });
64
+ });
65
+ it('should throw if callback is not a function', () => {
66
+ expect(() => [1, 2, 3].groupBy(null)).toThrowError(TypeError);
67
+ });
68
+ it('should throw if callback does not return string or number', () => {
69
+ expect(() => [1, 2, 3].groupBy(() => ({}))).toThrowError(TypeError);
70
+ });
71
+ });
72
+ describe('compact', () => {
73
+ it('should remove falsy values', () => {
74
+ expect([0, 1, false, 2, '', 3, null].compact()).toEqual([1, 2, 3]);
75
+ });
76
+ });
77
+ describe('swap', () => {
78
+ it('should swap two elements in the array', () => {
79
+ const arr = [1, 2, 3];
80
+ arr.swap(0, 2);
81
+ expect(arr).toEqual([3, 2, 1]);
82
+ });
83
+ it('should do nothing if indices are the same', () => {
84
+ const arr = [1, 2, 3];
85
+ arr.swap(1, 1);
86
+ expect(arr).toEqual([1, 2, 3]);
87
+ });
88
+ it('should throw if an index is out of bounds', () => {
89
+ const arr = [1, 2, 3];
90
+ expect(() => arr.swap(-1, 2)).toThrowError(RangeError);
91
+ expect(() => arr.swap(0, 3)).toThrowError(RangeError);
92
+ });
93
+ it('should throw if indices are not integers', () => {
94
+ const arr = [1, 2, 3];
95
+ expect(() => arr.swap(0.5, 2)).toThrowError(TypeError);
96
+ expect(() => arr.swap(0, 'a')).toThrowError(TypeError);
97
+ });
98
+ });
99
+ });
@@ -0,0 +1,4 @@
1
+ import './array/array-constructor';
2
+ import './array/array-prototype';
3
+ import './object/object-prototype';
4
+ import './string/string-prototype';
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ require("./array/array-constructor");
4
+ require("./array/array-prototype");
5
+ require("./object/object-prototype");
6
+ require("./string/string-prototype");
@@ -0,0 +1,22 @@
1
+ export {};
2
+ declare global {
3
+ interface Object {
4
+ /**
5
+ * Creates a deep clone of the object.
6
+ * @returns A deep copy of the original object.
7
+ */
8
+ deepClone<T>(): T;
9
+ /**
10
+ * Deeply merges another object into the current object.
11
+ * @param source - The object to merge from.
12
+ * @returns The merged object (this).
13
+ */
14
+ deepMerge<T>(source: any): T | typeof source;
15
+ /**
16
+ * Checks for deep equality with another object.
17
+ * @param other - The object to compare with.
18
+ * @returns True if deeply equal, false otherwise.
19
+ */
20
+ deepEquals(other: unknown): boolean;
21
+ }
22
+ }
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const defineIfNotExists = (name, fn) => {
4
+ const descriptor = Object.getOwnPropertyDescriptor(Object.prototype, name);
5
+ if (!descriptor || descriptor.writable || descriptor.configurable) {
6
+ Object.defineProperty(Object.prototype, name, {
7
+ value: fn,
8
+ enumerable: false,
9
+ configurable: true,
10
+ writable: true,
11
+ });
12
+ }
13
+ };
14
+ defineIfNotExists('deepClone', function () {
15
+ return structuredClone(this);
16
+ });
17
+ defineIfNotExists('deepMerge', function (source) {
18
+ if (typeof source !== 'object' || source === null) {
19
+ throw new TypeError('Source must be a non-null object');
20
+ }
21
+ const isObject = (val) => val !== null && typeof val === 'object' && !Array.isArray(val);
22
+ const merge = (target, source) => {
23
+ for (const key of Object.keys(source)) {
24
+ if (isObject(source[key])) {
25
+ if (!target[key])
26
+ Object.assign(target, { [key]: {} });
27
+ merge(target[key], source[key]);
28
+ }
29
+ else {
30
+ Object.assign(target, { [key]: source[key] });
31
+ }
32
+ }
33
+ return target;
34
+ };
35
+ return merge(this.deepClone(), source);
36
+ });
37
+ defineIfNotExists('deepEquals', function (other) {
38
+ function eq(a, b) {
39
+ // Functions: always false (even if code is the same)
40
+ if (typeof a === 'function' || typeof b === 'function') {
41
+ return false;
42
+ }
43
+ // Type check
44
+ if (typeof a !== typeof b)
45
+ return false;
46
+ if (a === b) {
47
+ // +0 !== -0
48
+ return a !== 0 || 1 / a === 1 / b;
49
+ }
50
+ // NaN === NaN
51
+ if (Number.isNaN(a) && Number.isNaN(b)) {
52
+ return true;
53
+ }
54
+ // Null check
55
+ if (a === null || b === null)
56
+ return a === b;
57
+ // Array and object check
58
+ if (Array.isArray(a) !== Array.isArray(b))
59
+ return false;
60
+ // Array check
61
+ if (Array.isArray(a) && Array.isArray(b)) {
62
+ if (a.length !== b.length)
63
+ return false;
64
+ for (let i = 0; i < a.length; i++) {
65
+ if (!eq(a[i], b[i]))
66
+ return false;
67
+ }
68
+ return true;
69
+ }
70
+ // Object check
71
+ if (typeof a === 'object' && typeof b === 'object') {
72
+ const aKeys = Object.keys(a);
73
+ const bKeys = Object.keys(b);
74
+ if (aKeys.length !== bKeys.length)
75
+ return false;
76
+ // Check for undefined vs missing keys
77
+ for (const key of aKeys) {
78
+ if (!b.hasOwnProperty(key))
79
+ return false;
80
+ if (!eq(a[key], b[key]))
81
+ return false;
82
+ }
83
+ return true;
84
+ }
85
+ return false;
86
+ }
87
+ return eq(this, other);
88
+ });
@@ -0,0 +1 @@
1
+ import './object-prototype';
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ require("./object-prototype");
4
+ describe('Object.prototype extensions', () => {
5
+ describe('deepClone', () => {
6
+ it('should deeply clone a plain object', () => {
7
+ const obj = { a: 1, b: { c: 2 } };
8
+ const clone = structuredClone(obj);
9
+ expect(clone).toEqual(obj);
10
+ expect(clone).not.toBe(obj);
11
+ expect(clone.b).not.toBe(obj.b);
12
+ });
13
+ it('should deeply clone an array', () => {
14
+ const arr = [{ a: 1 }, { b: 2 }];
15
+ const clone = arr.deepClone();
16
+ expect(clone).toEqual(arr);
17
+ expect(clone).not.toBe(arr);
18
+ expect(clone[0]).not.toBe(arr[0]);
19
+ });
20
+ });
21
+ describe('deepMerge', () => {
22
+ it('should deeply merge two objects', () => {
23
+ const obj = { a: 1, b: { c: 2 } };
24
+ const source = { b: { d: 3 }, e: 4 };
25
+ const merged = obj.deepMerge(source);
26
+ expect(merged).toEqual({ a: 1, b: { c: 2, d: 3 }, e: 4 });
27
+ console.log(merged);
28
+ });
29
+ it('should overwrite primitive values', () => {
30
+ const obj = { a: 1, b: 2 };
31
+ const merged = obj.deepMerge({ b: 3 });
32
+ expect(merged).toEqual({ a: 1, b: 3 });
33
+ console.log(merged);
34
+ });
35
+ it('should throw if source is not an object', () => {
36
+ expect(() => ({}).deepMerge(null)).toThrowError(TypeError);
37
+ expect(() => ({}).deepMerge(42)).toThrowError(TypeError);
38
+ });
39
+ });
40
+ describe('Object.prototype polyfills - deepEquals', () => {
41
+ it('should return true for objects with same keys and values, regardless of key order', () => {
42
+ expect({ a: 2, b: 3 }.deepEquals({ b: 3, a: 2 })).toBe(true);
43
+ expect({ a: 2, b: { c: 4 } }.deepEquals({ b: { c: 4 }, a: 2 })).toBe(true);
44
+ });
45
+ it('should return false for objects with different values', () => {
46
+ expect({ a: 2, b: 3 }.deepEquals({ a: 2, b: 4 })).toBe(false);
47
+ expect({ a: 2, b: { c: 4 } }.deepEquals({ a: 2, b: { c: 5 } })).toBe(false);
48
+ });
49
+ it('should return true for arrays with same elements in same order', () => {
50
+ expect([1, 2, 3].deepEquals([1, 2, 3])).toBe(true);
51
+ expect([1, [2, 3]].deepEquals([1, [2, 3]])).toBe(true);
52
+ });
53
+ it('should return false for arrays with different elements or order', () => {
54
+ expect([1, 2, 3].deepEquals([1, 3, 2])).toBe(false);
55
+ expect([1, [2, 3]].deepEquals([1, [3, 2]])).toBe(false);
56
+ });
57
+ it('should return false for objects with different keys', () => {
58
+ expect({ a: 2 }.deepEquals({ b: 2 })).toBe(false);
59
+ });
60
+ it('should return true for empty arrays and objects', () => {
61
+ expect([].deepEquals([])).toBe(true);
62
+ expect({}.deepEquals({})).toBe(true);
63
+ });
64
+ it('should return false for array vs object', () => {
65
+ expect([].deepEquals({})).toBe(false);
66
+ expect({}.deepEquals([])).toBe(false);
67
+ });
68
+ it('should return false for different lengths', () => {
69
+ expect([1].deepEquals([1, 2])).toBe(false);
70
+ expect({ a: 1 }.deepEquals({ a: 1, b: 2 })).toBe(false);
71
+ });
72
+ it('should return true for NaN deepEquals NaN', () => {
73
+ expect({ a: NaN }.deepEquals({ a: NaN })).toBe(true);
74
+ expect([NaN].deepEquals([NaN])).toBe(true);
75
+ });
76
+ it('should return false for +0 and -0', () => {
77
+ expect({ a: +0 }.deepEquals({ a: -0 })).toBe(false);
78
+ expect([+0].deepEquals([-0])).toBe(false);
79
+ });
80
+ it('should return false for objects with undefined vs missing keys', () => {
81
+ expect({ a: undefined }.deepEquals({})).toBe(false);
82
+ expect({}.deepEquals({ a: undefined })).toBe(false);
83
+ });
84
+ it('should return false for objects with functions, even if functions are equal', () => {
85
+ expect({ a: () => 1 }.deepEquals({ a: () => 1 })).toBe(false);
86
+ });
87
+ it('should return true for nested structures deeply equal', () => {
88
+ expect({ a: [1, { b: 2 }] }.deepEquals({ a: [1, { b: 2 }] })).toBe(true);
89
+ });
90
+ it('should return false for nested structures not deeply equal', () => {
91
+ expect({ a: [1, { b: 2 }] }.deepEquals({ a: [1, { b: 3 }] })).toBe(false);
92
+ });
93
+ });
94
+ });
@@ -0,0 +1,51 @@
1
+ export {};
2
+ declare global {
3
+ interface String {
4
+ /**
5
+ * Capitalizes the first character of the string.
6
+ * @returns A new string with the first character in uppercase.
7
+ */
8
+ capitalize(): string;
9
+ /**
10
+ * Splits the string into an array of words, detecting camelCase, kebab-case, snake_case, and spaces.
11
+ * @returns An array of words extracted from the string.
12
+ */
13
+ splitWords(): string[];
14
+ /**
15
+ * Converts the string to camelCase.
16
+ * @returns A camelCased version of the string.
17
+ */
18
+ camelCase(): string;
19
+ /**
20
+ * Converts the string to kebab-case.
21
+ * @returns A kebab-cased version of the string.
22
+ */
23
+ kebabCase(): string;
24
+ /**
25
+ * Converts the string to snake_case.
26
+ * @returns A snake_cased version of the string.
27
+ */
28
+ snakeCase(): string;
29
+ /**
30
+ * Truncates the string to a maximum number of characters, appending '...' if truncated.
31
+ * @param n - Maximum length of the string.
32
+ * @returns A truncated string.
33
+ */
34
+ truncate(n: number): string;
35
+ /**
36
+ * Reverses the characters in the string.
37
+ * @returns The reversed string.
38
+ */
39
+ reverse(): string;
40
+ /**
41
+ * Checks if the string is empty or contains only whitespace.
42
+ * @returns `true` if the string is empty or whitespace only, `false` otherwise.
43
+ */
44
+ isEmpty(): boolean;
45
+ /**
46
+ * Converts the string into a slug usable in URLs.
47
+ * @returns A slugified version of the string.
48
+ */
49
+ slugify(): string;
50
+ }
51
+ }
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const utils_1 = require("../utils");
4
+ (0, utils_1.defineIfNotExists)(String.prototype, 'splitWords', function () {
5
+ return this.replace(/([a-z0-9])([A-Z])/g, '$1 $2') // helloWorld → hello World
6
+ .replace(/([A-Z])([A-Z][a-z])/g, '$1 $2') // HTMLParser → HTML Parser
7
+ .replace(/[^a-zA-Z0-9]+/g, ' ')
8
+ .trim()
9
+ .split(/\s+/)
10
+ .filter(Boolean);
11
+ });
12
+ (0, utils_1.defineIfNotExists)(String.prototype, 'camelCase', function () {
13
+ const words = this.splitWords().map((w) => w.toLowerCase());
14
+ return words
15
+ .map((word, i) => i === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1))
16
+ .join('');
17
+ });
18
+ (0, utils_1.defineIfNotExists)(String.prototype, 'kebabCase', function () {
19
+ return this.splitWords()
20
+ .map((w) => w.toLowerCase())
21
+ .join('-');
22
+ });
23
+ (0, utils_1.defineIfNotExists)(String.prototype, 'snakeCase', function () {
24
+ return this.splitWords()
25
+ .map((w) => w.toLowerCase())
26
+ .join('_');
27
+ });
28
+ (0, utils_1.defineIfNotExists)(String.prototype, 'truncate', function (n) {
29
+ if (typeof n !== 'number' || !Number.isInteger(n) || n < 0) {
30
+ throw new TypeError('Truncate length must be a non-negative integer');
31
+ }
32
+ return this.length > n ? this.slice(0, n) + '...' : this.toString();
33
+ });
34
+ (0, utils_1.defineIfNotExists)(String.prototype, 'reverse', function () {
35
+ return [...this].reverse().join('');
36
+ });
37
+ (0, utils_1.defineIfNotExists)(String.prototype, 'isEmpty', function () {
38
+ return this.trim().length === 0;
39
+ });
40
+ (0, utils_1.defineIfNotExists)(String.prototype, 'slugify', function () {
41
+ return this.normalize('NFD')
42
+ .replace(/[̀-ͯ]/g, '')
43
+ .replace(/[^a-zA-Z0-9]+/g, '-')
44
+ .replace(/^-+|-+$/g, '')
45
+ .toLowerCase();
46
+ });
47
+ (0, utils_1.defineIfNotExists)(String.prototype, 'capitalize', function () {
48
+ if (this.length === 0)
49
+ return '';
50
+ return this.charAt(0).toUpperCase() + this.slice(1);
51
+ });
@@ -0,0 +1 @@
1
+ import './string-prototype';
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ require("./string-prototype");
4
+ describe('String prototype extensions', () => {
5
+ describe('capitalize', () => {
6
+ it('should capitalize the first character', () => {
7
+ expect('hello'.capitalize()).toBe('Hello');
8
+ expect('Hello'.capitalize()).toBe('Hello');
9
+ expect(''.capitalize()).toBe('');
10
+ });
11
+ });
12
+ describe('splitWords', () => {
13
+ it('should split camelCase, kebab-case, snake_case, and spaces', () => {
14
+ expect('helloWorld'.splitWords()).toEqual(['hello', 'World']);
15
+ expect('hello-world'.splitWords()).toEqual(['hello', 'world']);
16
+ expect('hello_world'.splitWords()).toEqual(['hello', 'world']);
17
+ expect('hello world'.splitWords()).toEqual(['hello', 'world']);
18
+ expect('HTMLParser'.splitWords()).toEqual(['HTML', 'Parser']);
19
+ });
20
+ });
21
+ describe('camelCase', () => {
22
+ it('should convert to camelCase', () => {
23
+ expect('hello world'.camelCase()).toBe('helloWorld');
24
+ expect('Hello_world-test'.camelCase()).toBe('helloWorldTest');
25
+ expect('helloWorldTest'.camelCase()).toBe('helloWorldTest');
26
+ });
27
+ });
28
+ describe('kebabCase', () => {
29
+ it('should convert to kebab-case', () => {
30
+ expect('hello world'.kebabCase()).toBe('hello-world');
31
+ expect('Hello_worldTest'.kebabCase()).toBe('hello-world-test');
32
+ expect('hello-world-test'.kebabCase()).toBe('hello-world-test');
33
+ });
34
+ });
35
+ describe('snakeCase', () => {
36
+ it('should convert to snake_case', () => {
37
+ expect('hello world'.snakeCase()).toBe('hello_world');
38
+ expect('Hello-worldTest'.snakeCase()).toBe('hello_world_test');
39
+ expect('hello_world_test'.snakeCase()).toBe('hello_world_test');
40
+ });
41
+ });
42
+ describe('truncate', () => {
43
+ it('should truncate and add ... if needed', () => {
44
+ expect('hello world'.truncate(5)).toBe('hello...');
45
+ expect('hello'.truncate(10)).toBe('hello');
46
+ });
47
+ it('should throw if n is not a non-negative integer', () => {
48
+ expect(() => 'abc'.truncate(-1)).toThrowError(TypeError);
49
+ expect(() => 'abc'.truncate(1.5)).toThrowError(TypeError);
50
+ expect(() => 'abc'.truncate('a')).toThrowError(TypeError);
51
+ });
52
+ });
53
+ describe('reverse', () => {
54
+ it('should reverse the string', () => {
55
+ expect('abc'.reverse()).toBe('cba');
56
+ expect('été'.reverse()).toBe('été');
57
+ });
58
+ });
59
+ describe('isEmpty', () => {
60
+ it('should return true for empty or whitespace strings', () => {
61
+ expect(''.isEmpty()).toBe(true);
62
+ expect(' '.isEmpty()).toBe(true);
63
+ });
64
+ it('should return false for non-empty strings', () => {
65
+ expect('abc'.isEmpty()).toBe(false);
66
+ expect(' a '.isEmpty()).toBe(false);
67
+ });
68
+ });
69
+ describe('slugify', () => {
70
+ it('should slugify a string', () => {
71
+ expect('Hello World!'.slugify()).toBe('hello-world');
72
+ expect("Éléphant à l'été".slugify()).toBe('elephant-a-l-ete');
73
+ expect(' --Hello__World-- '.slugify()).toBe('hello-world');
74
+ });
75
+ });
76
+ });
@@ -0,0 +1 @@
1
+ export declare const defineIfNotExists: <T extends object>(prototype: T, name: string, fn: Function) => void;
package/dist/utils.js ADDED
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.defineIfNotExists = void 0;
4
+ const defineIfNotExists = (prototype, name, fn) => {
5
+ const descriptor = Object.getOwnPropertyDescriptor(prototype, name);
6
+ if (!descriptor || descriptor.writable || descriptor.configurable) {
7
+ Object.defineProperty(prototype, name, {
8
+ value: fn,
9
+ enumerable: false,
10
+ configurable: false,
11
+ writable: false,
12
+ });
13
+ }
14
+ };
15
+ exports.defineIfNotExists = defineIfNotExists;
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "utilitish",
3
+ "version": "0.0.1",
4
+ "description": "",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "scripts": {
11
+ "build": "tsc"
12
+ },
13
+ "keywords": [
14
+ "typescript",
15
+ "prototype",
16
+ "utils"
17
+ ],
18
+ "author": "Donovan Ferreira <donovanferreira12@gmail.com> (https://github.com/FDonovan12)",
19
+ "license": "GPL-3.0",
20
+ "devDependencies": {
21
+ "@types/jest": "^29.5.14",
22
+ "typescript": "^5.8.3"
23
+ }
24
+ }