utilitish 0.0.1 → 0.0.2

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 CHANGED
@@ -1,25 +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/>.
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 CHANGED
@@ -1,5 +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.
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.
@@ -57,6 +57,26 @@ declare global {
57
57
  * @returns An array of [value, index] pairs.
58
58
  */
59
59
  enumerate(): [T, number][];
60
+ /**
61
+ * Returns a sorted copy of the array in ascending order.
62
+ * - If no callback is provided, all elements must be of type `number` or `string`.
63
+ * - If a callback is provided, it must return a `number` or `string` for each element.
64
+ * @param callback Optional function to extract the value to sort by.
65
+ * @throws {TypeError} If elements are not sortable or the callback returns an invalid type.
66
+ * @returns A new array sorted in ascending order.
67
+ */
68
+ sortAsc(this: number[] | string[]): T[];
69
+ sortAsc(this: T[], callback: (item: T) => number | string): T[];
70
+ /**
71
+ * Returns a sorted copy of the array in descending order.
72
+ * - If no callback is provided, all elements must be of type `number` or `string`.
73
+ * - If a callback is provided, it must return a `number` or `string` for each element.
74
+ * @param callback Optional function to extract the value to sort by.
75
+ * @throws {TypeError} If elements are not sortable or the callback returns an invalid type.
76
+ * @returns A new array sorted in descending order.
77
+ */
78
+ sortDesc(this: number[] | string[]): T[];
79
+ sortDesc(this: T[], callback: (item: T) => number | string): T[];
60
80
  /**
61
81
  * Swaps the values at two indices in the array.
62
82
  * @param i First index
@@ -36,7 +36,7 @@ const utils_1 = require("../utils");
36
36
  return this.reduce((acc, item) => acc + callback(item), 0) / this.length;
37
37
  }
38
38
  if (this.every((item) => typeof item === 'number')) {
39
- return (this.reduce((acc, item) => acc + item, 0) / this.length);
39
+ return this.reduce((acc, item) => acc + item, 0) / this.length;
40
40
  }
41
41
  throw new Error('Array.prototype.average() requires a callback unless array is number[]');
42
42
  });
@@ -62,11 +62,57 @@ const utils_1 = require("../utils");
62
62
  (0, utils_1.defineIfNotExists)(Array.prototype, 'enumerate', function () {
63
63
  return this.map((value, index) => [value, index]);
64
64
  });
65
+ (0, utils_1.defineIfNotExists)(Array.prototype, 'sortAsc', function (callback) {
66
+ const arr = this.slice();
67
+ if (arr.length === 0)
68
+ return arr;
69
+ if (callback && typeof callback !== 'function') {
70
+ throw new TypeError('Callback must be a function');
71
+ }
72
+ // Si pas de callback, vérifier que tous les éléments sont number ou string
73
+ if (!callback && !arr.every((item) => typeof item === 'number' || typeof item === 'string')) {
74
+ throw new TypeError('Array elements must be number or string if no callback is provided');
75
+ }
76
+ return arr.sort((a, b) => {
77
+ const valA = callback ? callback(a) : a;
78
+ const valB = callback ? callback(b) : b;
79
+ if ((typeof valA !== 'number' && typeof valA !== 'string') ||
80
+ (typeof valB !== 'number' && typeof valB !== 'string')) {
81
+ throw new TypeError('Callback must return number or string');
82
+ }
83
+ if (valA < valB)
84
+ return -1;
85
+ if (valA > valB)
86
+ return 1;
87
+ return 0;
88
+ });
89
+ });
90
+ (0, utils_1.defineIfNotExists)(Array.prototype, 'sortDesc', function (callback) {
91
+ const arr = this.slice();
92
+ if (arr.length === 0)
93
+ return arr;
94
+ if (callback && typeof callback !== 'function') {
95
+ throw new TypeError('Callback must be a function');
96
+ }
97
+ if (!callback && !arr.every((item) => typeof item === 'number' || typeof item === 'string')) {
98
+ throw new TypeError('Array elements must be number or string if no callback is provided');
99
+ }
100
+ return arr.sort((a, b) => {
101
+ const valA = callback ? callback(a) : a;
102
+ const valB = callback ? callback(b) : b;
103
+ if ((typeof valA !== 'number' && typeof valA !== 'string') ||
104
+ (typeof valB !== 'number' && typeof valB !== 'string')) {
105
+ throw new TypeError('Callback must return number or string');
106
+ }
107
+ if (valA > valB)
108
+ return -1;
109
+ if (valA < valB)
110
+ return 1;
111
+ return 0;
112
+ });
113
+ });
65
114
  (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)) {
115
+ if (typeof i !== 'number' || typeof j !== 'number' || !Number.isInteger(i) || !Number.isInteger(j)) {
70
116
  throw new TypeError('Indices must be integers');
71
117
  }
72
118
  if (i < 0 || i >= this.length || j < 0 || j >= this.length) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "utilitish",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -8,7 +8,8 @@
8
8
  "dist"
9
9
  ],
10
10
  "scripts": {
11
- "build": "tsc"
11
+ "build": "tsc",
12
+ "test": "jest"
12
13
  },
13
14
  "keywords": [
14
15
  "typescript",
@@ -19,6 +20,8 @@
19
20
  "license": "GPL-3.0",
20
21
  "devDependencies": {
21
22
  "@types/jest": "^29.5.14",
23
+ "jest": "^29.7.0",
24
+ "ts-jest": "^29.3.4",
22
25
  "typescript": "^5.8.3"
23
26
  }
24
27
  }
@@ -1 +0,0 @@
1
- import './array-constructor';
@@ -1,75 +0,0 @@
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
- });
@@ -1 +0,0 @@
1
- import './array-prototype';
@@ -1,99 +0,0 @@
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
- });
@@ -1 +0,0 @@
1
- import './object-prototype';
@@ -1,94 +0,0 @@
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
- });
@@ -1 +0,0 @@
1
- import './string-prototype';
@@ -1,76 +0,0 @@
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
- });