utilitish 0.0.11 → 0.0.15
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 +25 -25
- package/README.md +68 -68
- package/dist/array/array-constructor.js +8 -4
- package/dist/array/array-prototype.d.ts +21 -0
- package/dist/array/array-prototype.js +94 -17
- package/dist/array/array-prototype.spec.js +16 -16
- package/dist/map/map-prototype.js +18 -18
- package/dist/object/object-prototype.js +4 -3
- package/dist/set/set-prototype.d.ts +1 -1
- package/dist/set/set-prototype.js +4 -4
- package/dist/string/string-prototype.d.ts +88 -11
- package/dist/string/string-prototype.js +59 -19
- package/dist/string/string-prototype.spec.js +20 -20
- package/dist/test.html +13 -0
- package/dist/utils/core.utils.d.ts +1 -0
- package/dist/utils/core.utils.js +6 -1
- package/dist/utils/core.utils.spec.d.ts +1 -0
- package/dist/utils/core.utils.spec.js +150 -0
- package/dist/utils/logic.utils.js +4 -0
- package/dist/utils/logic.utils.spec.d.ts +1 -0
- package/dist/utils/logic.utils.spec.js +57 -0
- package/dist/utils/slugify.config.d.ts +9 -0
- package/dist/utils/slugify.config.js +15 -0
- package/dist/utils/slugify.config.spec.d.ts +1 -0
- package/dist/utils/slugify.config.spec.js +144 -0
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +15 -0
- package/package.json +4 -2
|
@@ -11,9 +11,10 @@ const core_utils_1 = require("../utils/core.utils");
|
|
|
11
11
|
* @see Object.prototype.deepMerge
|
|
12
12
|
*/
|
|
13
13
|
(0, core_utils_1.defineIfNotExists)(Object.prototype, 'deepMerge', function (source) {
|
|
14
|
-
if (typeof source !== 'object'
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
if (typeof source !== 'object')
|
|
15
|
+
(0, core_utils_1.utilitishError)('Object.prototype.deepMerge', 'source must be an object', source);
|
|
16
|
+
if (source === null)
|
|
17
|
+
(0, core_utils_1.utilitishError)('Object.prototype.deepMerge', 'source cannot be null', source);
|
|
17
18
|
const isObject = (val) => val !== null && typeof val === 'object' && !Array.isArray(val);
|
|
18
19
|
const merge = (target, source) => {
|
|
19
20
|
for (const key of Object.keys(source)) {
|
|
@@ -15,7 +15,7 @@ declare global {
|
|
|
15
15
|
* - Returns a new array instance each time; modifying it does not affect the Set
|
|
16
16
|
* - Empty sets return an empty array
|
|
17
17
|
*/
|
|
18
|
-
toList
|
|
18
|
+
toList(): T[];
|
|
19
19
|
/**
|
|
20
20
|
* Returns true if at least one of the given items is present in the Set.
|
|
21
21
|
*
|
|
@@ -13,7 +13,7 @@ const core_utils_1 = require("../utils/core.utils");
|
|
|
13
13
|
*/
|
|
14
14
|
(0, core_utils_1.defineIfNotExists)(Set.prototype, 'hasAny', function (...items) {
|
|
15
15
|
if (!Array.isArray(items))
|
|
16
|
-
|
|
16
|
+
(0, core_utils_1.utilitishError)('Set.prototype.hasAny', 'arguments must be an array', items);
|
|
17
17
|
if (items.length === 0)
|
|
18
18
|
return false;
|
|
19
19
|
return items.some((item) => this.has(item));
|
|
@@ -32,7 +32,7 @@ const core_utils_1 = require("../utils/core.utils");
|
|
|
32
32
|
values = args;
|
|
33
33
|
}
|
|
34
34
|
if (!Array.isArray(values))
|
|
35
|
-
|
|
35
|
+
(0, core_utils_1.utilitishError)('Set.prototype.includes', 'arguments must be an array or a Set', args);
|
|
36
36
|
return values.every((item) => this.has(item));
|
|
37
37
|
});
|
|
38
38
|
/**
|
|
@@ -42,7 +42,7 @@ const core_utils_1 = require("../utils/core.utils");
|
|
|
42
42
|
const result = new Set(this);
|
|
43
43
|
for (const other of others) {
|
|
44
44
|
if (!(other instanceof Set))
|
|
45
|
-
|
|
45
|
+
(0, core_utils_1.utilitishError)('Set.prototype.union', 'arguments must be Sets', other);
|
|
46
46
|
for (const item of other) {
|
|
47
47
|
result.add(item);
|
|
48
48
|
}
|
|
@@ -54,7 +54,7 @@ const core_utils_1 = require("../utils/core.utils");
|
|
|
54
54
|
*/
|
|
55
55
|
(0, core_utils_1.defineIfNotExists)(Set.prototype, 'intersection', function (...others) {
|
|
56
56
|
if (others.some((s) => !(s instanceof Set)))
|
|
57
|
-
|
|
57
|
+
(0, core_utils_1.utilitishError)('Set.prototype.intersection', 'arguments must be Sets', others);
|
|
58
58
|
const result = new Set();
|
|
59
59
|
for (const item of this) {
|
|
60
60
|
if (others.every((set) => set.has(item))) {
|
|
@@ -198,22 +198,99 @@ declare global {
|
|
|
198
198
|
* Compares two strings by slugifying both and checking if they are equal.
|
|
199
199
|
* Useful for case-insensitive and accent-insensitive string comparison.
|
|
200
200
|
*
|
|
201
|
-
* @this {string} The first string to compare
|
|
202
|
-
* @param {string} other - The second string to compare
|
|
203
|
-
* @returns {boolean}
|
|
204
|
-
* @throws {TypeError} If
|
|
201
|
+
* @this {string} The first string to compare.
|
|
202
|
+
* @param {string} other - The second string to compare.
|
|
203
|
+
* @returns {boolean} `true` if both slugified strings are equal, `false` otherwise.
|
|
204
|
+
* @throws {TypeError} If `other` is not a string.
|
|
205
205
|
*
|
|
206
206
|
* @example
|
|
207
|
-
* 'Hello World'.
|
|
208
|
-
* 'Héllo
|
|
209
|
-
* 'Hello World'.
|
|
207
|
+
* 'Hello World'.slugifyEquals('hello-world'); // true
|
|
208
|
+
* 'Héllo World'.slugifyEquals('hello-world'); // true
|
|
209
|
+
* 'Hello World'.slugifyEquals('goodbye-world'); // false
|
|
210
210
|
*
|
|
211
211
|
* @remarks
|
|
212
|
-
* - Both strings are slugified
|
|
213
|
-
* -
|
|
214
|
-
* -
|
|
212
|
+
* - Both strings are slugified before comparison.
|
|
213
|
+
* - Since `slugify` is idempotent, passing an already-slugified string works as expected.
|
|
214
|
+
* - Useful for comparing user-provided strings with stored slugs.
|
|
215
215
|
*/
|
|
216
|
-
|
|
216
|
+
slugifyEquals(other: string): boolean;
|
|
217
|
+
/**
|
|
218
|
+
* Checks if the slugified version of this string includes the slugified version of another.
|
|
219
|
+
* Useful for case-insensitive and accent-insensitive substring search.
|
|
220
|
+
*
|
|
221
|
+
* @this {string} The string to search in.
|
|
222
|
+
* @param {string} other - The string to search for.
|
|
223
|
+
* @returns {boolean} `true` if the slugified string contains the slugified `other`, `false` otherwise.
|
|
224
|
+
* @throws {TypeError} If `other` is not a string.
|
|
225
|
+
*
|
|
226
|
+
* @example
|
|
227
|
+
* 'Hello World'.slugifyIncludes('hello'); // true
|
|
228
|
+
* 'Héllo World'.slugifyIncludes('hello'); // true
|
|
229
|
+
* 'Hello World'.slugifyIncludes('goodbye'); // false
|
|
230
|
+
*
|
|
231
|
+
* @remarks
|
|
232
|
+
* - Both strings are slugified before comparison.
|
|
233
|
+
* - Since `slugify` is idempotent, passing an already-slugified string works as expected.
|
|
234
|
+
*/
|
|
235
|
+
slugifyIncludes(other: string): boolean;
|
|
236
|
+
/**
|
|
237
|
+
* Checks if the slugified version of this string starts with the slugified version of another.
|
|
238
|
+
* Useful for case-insensitive and accent-insensitive prefix matching.
|
|
239
|
+
*
|
|
240
|
+
* @this {string} The string to check.
|
|
241
|
+
* @param {string} other - The prefix to check against.
|
|
242
|
+
* @returns {boolean} `true` if the slugified string starts with the slugified `other`, `false` otherwise.
|
|
243
|
+
* @throws {TypeError} If `other` is not a string.
|
|
244
|
+
*
|
|
245
|
+
* @example
|
|
246
|
+
* 'Hello World'.slugifyStartsWith('hello'); // true
|
|
247
|
+
* 'Héllo World'.slugifyStartsWith('hello'); // true
|
|
248
|
+
* 'Hello World'.slugifyStartsWith('world'); // false
|
|
249
|
+
*
|
|
250
|
+
* @remarks
|
|
251
|
+
* - Both strings are slugified before comparison.
|
|
252
|
+
* - Since `slugify` is idempotent, passing an already-slugified string works as expected.
|
|
253
|
+
*/
|
|
254
|
+
slugifyStartsWith(other: string): boolean;
|
|
255
|
+
/**
|
|
256
|
+
* Checks if the slugified version of this string ends with the slugified version of another.
|
|
257
|
+
* Useful for case-insensitive and accent-insensitive suffix matching.
|
|
258
|
+
*
|
|
259
|
+
* @this {string} The string to check.
|
|
260
|
+
* @param {string} other - The suffix to check against.
|
|
261
|
+
* @returns {boolean} `true` if the slugified string ends with the slugified `other`, `false` otherwise.
|
|
262
|
+
* @throws {TypeError} If `other` is not a string.
|
|
263
|
+
*
|
|
264
|
+
* @example
|
|
265
|
+
* 'Hello World'.slugifyEndsWith('world'); // true
|
|
266
|
+
* 'Héllo World'.slugifyEndsWith('world'); // true
|
|
267
|
+
* 'Hello World'.slugifyEndsWith('hello'); // false
|
|
268
|
+
*
|
|
269
|
+
* @remarks
|
|
270
|
+
* - Both strings are slugified before comparison.
|
|
271
|
+
* - Since `slugify` is idempotent, passing an already-slugified string works as expected.
|
|
272
|
+
*/
|
|
273
|
+
slugifyEndsWith(other: string): boolean;
|
|
274
|
+
/**
|
|
275
|
+
* Checks if the slugified version of this string matches any slugified string in a list.
|
|
276
|
+
* Useful for case-insensitive and accent-insensitive list lookup.
|
|
277
|
+
*
|
|
278
|
+
* @this {string} The string to search for.
|
|
279
|
+
* @param {string[]} list - The array of strings to search in.
|
|
280
|
+
* @returns {boolean} `true` if any slugified item in the list equals the slugified string, `false` otherwise.
|
|
281
|
+
* @throws {TypeError} If `list` is not an array.
|
|
282
|
+
* @throws {TypeError} If `list` contains non-string items.
|
|
283
|
+
*
|
|
284
|
+
* @example
|
|
285
|
+
* 'Hello World'.slugifyIn(['hello-world', 'foo']); // true
|
|
286
|
+
* 'Héllo World'.slugifyIn(['hello-world', 'foo']); // true
|
|
287
|
+
* 'Hello World'.slugifyIn(['foo', 'bar']); // false
|
|
288
|
+
*
|
|
289
|
+
* @remarks
|
|
290
|
+
* - All strings are slugified before comparison.
|
|
291
|
+
* - Since `slugify` is idempotent, mixing raw and already-slugified strings in the list works as expected.
|
|
292
|
+
*/
|
|
293
|
+
slugifyIn(list: string[]): boolean;
|
|
217
294
|
/**
|
|
218
295
|
* Replaces a substring between `start` and `end` indices with a given string.
|
|
219
296
|
* Provides precise control over which portion of the string to replace.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const core_utils_1 = require("../utils/core.utils");
|
|
4
|
-
const
|
|
4
|
+
const slugify_config_1 = require("../utils/slugify.config");
|
|
5
5
|
/**
|
|
6
6
|
* @see String.prototype.splitWords
|
|
7
7
|
*/
|
|
@@ -42,9 +42,12 @@ const slugify_utils_1 = require("../utils/slugify.utils");
|
|
|
42
42
|
* @see String.prototype.truncate
|
|
43
43
|
*/
|
|
44
44
|
(0, core_utils_1.defineIfNotExists)(String.prototype, 'truncate', function (n) {
|
|
45
|
-
if (typeof n !== 'number'
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
if (typeof n !== 'number')
|
|
46
|
+
(0, core_utils_1.utilitishError)('String.prototype.truncate', 'n must be a number', n);
|
|
47
|
+
if (!Number.isInteger(n))
|
|
48
|
+
(0, core_utils_1.utilitishError)('String.prototype.truncate', 'n must be an integer', n);
|
|
49
|
+
if (n < 0)
|
|
50
|
+
(0, core_utils_1.utilitishError)('String.prototype.truncate', 'n must be non-negative', n);
|
|
48
51
|
return this.length > n ? this.slice(0, n) + '...' : this.toString();
|
|
49
52
|
});
|
|
50
53
|
/**
|
|
@@ -63,7 +66,7 @@ const slugify_utils_1 = require("../utils/slugify.utils");
|
|
|
63
66
|
* @see String.prototype.slugify
|
|
64
67
|
*/
|
|
65
68
|
(0, core_utils_1.defineIfNotExists)(String.prototype, 'slugify', function (config) {
|
|
66
|
-
return (0,
|
|
69
|
+
return (0, slugify_config_1.slugifyString)(this, config);
|
|
67
70
|
});
|
|
68
71
|
/**
|
|
69
72
|
* @see String.prototype.capitalize
|
|
@@ -74,27 +77,64 @@ const slugify_utils_1 = require("../utils/slugify.utils");
|
|
|
74
77
|
return this.charAt(0).toUpperCase() + this.slice(1);
|
|
75
78
|
});
|
|
76
79
|
/**
|
|
77
|
-
* @see String.prototype.
|
|
80
|
+
* @see String.prototype.slugifyEquals
|
|
78
81
|
*/
|
|
79
|
-
(0, core_utils_1.defineIfNotExists)(String.prototype, '
|
|
80
|
-
if (typeof other !== 'string')
|
|
81
|
-
|
|
82
|
-
}
|
|
82
|
+
(0, core_utils_1.defineIfNotExists)(String.prototype, 'slugifyEquals', function (other) {
|
|
83
|
+
if (typeof other !== 'string')
|
|
84
|
+
(0, core_utils_1.utilitishError)('String.prototype.slugifyEquals', 'other must be a string', other);
|
|
83
85
|
return this.slugify() === other.slugify();
|
|
84
86
|
});
|
|
87
|
+
/**
|
|
88
|
+
* @see String.prototype.slugifyIncludes
|
|
89
|
+
*/
|
|
90
|
+
(0, core_utils_1.defineIfNotExists)(String.prototype, 'slugifyIncludes', function (other) {
|
|
91
|
+
if (typeof other !== 'string')
|
|
92
|
+
(0, core_utils_1.utilitishError)('String.prototype.slugifyIncludes', 'other must be a string', other);
|
|
93
|
+
return this.slugify().includes(other.slugify());
|
|
94
|
+
});
|
|
95
|
+
/**
|
|
96
|
+
* @see String.prototype.slugifyStartsWith
|
|
97
|
+
*/
|
|
98
|
+
(0, core_utils_1.defineIfNotExists)(String.prototype, 'slugifyStartsWith', function (other) {
|
|
99
|
+
if (typeof other !== 'string')
|
|
100
|
+
(0, core_utils_1.utilitishError)('String.prototype.slugifyStartsWith', 'other must be a string', other);
|
|
101
|
+
return this.slugify().startsWith(other.slugify());
|
|
102
|
+
});
|
|
103
|
+
/**
|
|
104
|
+
* @see String.prototype.slugifyEndsWith
|
|
105
|
+
*/
|
|
106
|
+
(0, core_utils_1.defineIfNotExists)(String.prototype, 'slugifyEndsWith', function (other) {
|
|
107
|
+
if (typeof other !== 'string')
|
|
108
|
+
(0, core_utils_1.utilitishError)('String.prototype.slugifyEndsWith', 'other must be a string', other);
|
|
109
|
+
return this.slugify().endsWith(other.slugify());
|
|
110
|
+
});
|
|
111
|
+
/**
|
|
112
|
+
* @see String.prototype.slugifyIn
|
|
113
|
+
*/
|
|
114
|
+
(0, core_utils_1.defineIfNotExists)(String.prototype, 'slugifyIn', function (list) {
|
|
115
|
+
if (!Array.isArray(list))
|
|
116
|
+
(0, core_utils_1.utilitishError)('String.prototype.slugifyIn', 'list must be an array', list);
|
|
117
|
+
if (!list.every((item) => typeof item === 'string'))
|
|
118
|
+
(0, core_utils_1.utilitishError)('String.prototype.slugifyIn', 'list must be an array of strings', list);
|
|
119
|
+
const slugified = this.slugify();
|
|
120
|
+
return list.some((item) => item.slugify() === slugified);
|
|
121
|
+
});
|
|
85
122
|
/**
|
|
86
123
|
* @see String.prototype.replaceRange
|
|
87
124
|
*/
|
|
88
125
|
(0, core_utils_1.defineIfNotExists)(String.prototype, 'replaceRange', function (start, end, replaceString = '') {
|
|
89
|
-
if (!Number.isInteger(start)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (
|
|
96
|
-
|
|
97
|
-
|
|
126
|
+
if (!Number.isInteger(start))
|
|
127
|
+
(0, core_utils_1.utilitishError)('String.prototype.replaceRange', 'start must be an integer', start);
|
|
128
|
+
if (!Number.isInteger(end))
|
|
129
|
+
(0, core_utils_1.utilitishError)('String.prototype.replaceRange', 'end must be an integer', end);
|
|
130
|
+
if (start < 0)
|
|
131
|
+
(0, core_utils_1.utilitishError)('String.prototype.replaceRange', 'start cannot be negative', start, RangeError);
|
|
132
|
+
if (end < 0)
|
|
133
|
+
(0, core_utils_1.utilitishError)('String.prototype.replaceRange', 'end cannot be negative', end, RangeError);
|
|
134
|
+
if (start > this.length)
|
|
135
|
+
(0, core_utils_1.utilitishError)('String.prototype.replaceRange', 'start is out of bounds', start, RangeError);
|
|
136
|
+
if (end > this.length)
|
|
137
|
+
(0, core_utils_1.utilitishError)('String.prototype.replaceRange', 'end is out of bounds', end, RangeError);
|
|
98
138
|
if (start > end)
|
|
99
139
|
[start, end] = [end, start];
|
|
100
140
|
return this.slice(0, start) + (replaceString ?? '') + this.slice(end);
|
|
@@ -161,40 +161,40 @@ describe('String.prototype', () => {
|
|
|
161
161
|
});
|
|
162
162
|
});
|
|
163
163
|
});
|
|
164
|
-
describe('
|
|
164
|
+
describe('slugifyEquals()', () => {
|
|
165
165
|
beforeEach(() => {
|
|
166
166
|
(0, slugify_config_1.resetSlugifyConfig)(); // Reset to defaults before each test
|
|
167
167
|
});
|
|
168
168
|
it('should return true when slugified strings are equal', () => {
|
|
169
|
-
expect('Hello World'.
|
|
170
|
-
expect('hello world'.
|
|
171
|
-
expect('HELLO WORLD'.
|
|
169
|
+
expect('Hello World'.slugifyEquals('hello-world')).toBe(true);
|
|
170
|
+
expect('hello world'.slugifyEquals('hello-world')).toBe(true);
|
|
171
|
+
expect('HELLO WORLD'.slugifyEquals('hello-world')).toBe(true);
|
|
172
172
|
});
|
|
173
173
|
it('should handle accents and special characters', () => {
|
|
174
|
-
expect('Café'.
|
|
175
|
-
expect('Éléphant'.
|
|
176
|
-
expect('naïve café'.
|
|
177
|
-
expect('résumé'.
|
|
174
|
+
expect('Café'.slugifyEquals('cafe')).toBe(true);
|
|
175
|
+
expect('Éléphant'.slugifyEquals('elephant')).toBe(true);
|
|
176
|
+
expect('naïve café'.slugifyEquals('naive-cafe')).toBe(true);
|
|
177
|
+
expect('résumé'.slugifyEquals('resume')).toBe(true);
|
|
178
178
|
});
|
|
179
179
|
it('should return false when slugified strings are different', () => {
|
|
180
|
-
expect('Hello World'.
|
|
181
|
-
expect('test string'.
|
|
180
|
+
expect('Hello World'.slugifyEquals('goodbye-world')).toBe(false);
|
|
181
|
+
expect('test string'.slugifyEquals('different-string')).toBe(false);
|
|
182
182
|
});
|
|
183
183
|
it('should handle extra spaces and special characters', () => {
|
|
184
|
-
expect(' Hello --- World '.
|
|
185
|
-
expect('Hello!!!World???'.
|
|
184
|
+
expect(' Hello --- World '.slugifyEquals('hello-world')).toBe(true);
|
|
185
|
+
expect('Hello!!!World???'.slugifyEquals('hello-world')).toBe(true);
|
|
186
186
|
});
|
|
187
187
|
it('should throw TypeError if parameter is not a string', () => {
|
|
188
|
-
expect(() => 'hello'.
|
|
189
|
-
expect(() => 'hello'.
|
|
190
|
-
expect(() => 'hello'.
|
|
191
|
-
expect(() => 'hello'.
|
|
192
|
-
expect(() => 'hello'.
|
|
188
|
+
expect(() => 'hello'.slugifyEquals(123)).toThrowError(TypeError);
|
|
189
|
+
expect(() => 'hello'.slugifyEquals(null)).toThrowError(TypeError);
|
|
190
|
+
expect(() => 'hello'.slugifyEquals(undefined)).toThrowError(TypeError);
|
|
191
|
+
expect(() => 'hello'.slugifyEquals({})).toThrowError(TypeError);
|
|
192
|
+
expect(() => 'hello'.slugifyEquals([])).toThrowError(TypeError);
|
|
193
193
|
});
|
|
194
194
|
it('should work with empty strings', () => {
|
|
195
|
-
expect(''.
|
|
196
|
-
expect(' '.
|
|
197
|
-
expect('hello'.
|
|
195
|
+
expect(''.slugifyEquals('')).toBe(true);
|
|
196
|
+
expect(' '.slugifyEquals('')).toBe(true);
|
|
197
|
+
expect('hello'.slugifyEquals('')).toBe(false);
|
|
198
198
|
});
|
|
199
199
|
});
|
|
200
200
|
describe('replaceRange()', () => {
|
package/dist/test.html
ADDED
|
@@ -101,3 +101,4 @@ export declare function isNumberOrString(value: unknown): value is string | numb
|
|
|
101
101
|
* Type for a selector: either a key of T or a function from T to K.
|
|
102
102
|
*/
|
|
103
103
|
export type Selector<T, K> = keyof T | ((item: T) => K);
|
|
104
|
+
export declare const utilitishError: (method: string, message: string, received?: unknown, ErrorClass?: TypeErrorConstructor) => never;
|
package/dist/utils/core.utils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.defineStaticIfNotExists = exports.defineIfNotExists = void 0;
|
|
3
|
+
exports.utilitishError = exports.defineStaticIfNotExists = exports.defineIfNotExists = void 0;
|
|
4
4
|
exports.resolveSelector = resolveSelector;
|
|
5
5
|
exports.assertValidSelector = assertValidSelector;
|
|
6
6
|
exports.isNumberOrString = isNumberOrString;
|
|
@@ -150,3 +150,8 @@ function assertValidSelector(selector, name = 'selector') {
|
|
|
150
150
|
function isNumberOrString(value) {
|
|
151
151
|
return typeof value === 'string' || typeof value === 'number';
|
|
152
152
|
}
|
|
153
|
+
const utilitishError = (method, message, received, ErrorClass = TypeError) => {
|
|
154
|
+
const receivedInfo = received !== undefined ? `, received ${ErrorClass === RangeError ? received : typeof received}` : '';
|
|
155
|
+
throw new ErrorClass(`[Utilitish] ${method}: ${message}${receivedInfo}`);
|
|
156
|
+
};
|
|
157
|
+
exports.utilitishError = utilitishError;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_utils_1 = require("./core.utils");
|
|
4
|
+
describe('core.utils', () => {
|
|
5
|
+
describe('defineIfNotExists()', () => {
|
|
6
|
+
it('should define a new method on a prototype', () => {
|
|
7
|
+
const proto = {};
|
|
8
|
+
(0, core_utils_1.defineIfNotExists)(proto, 'hello', () => 'world');
|
|
9
|
+
expect(proto.hello()).toBe('world');
|
|
10
|
+
});
|
|
11
|
+
it('should not overwrite a non-configurable non-writable property', () => {
|
|
12
|
+
const proto = {};
|
|
13
|
+
Object.defineProperty(proto, 'locked', {
|
|
14
|
+
value: () => 'original',
|
|
15
|
+
writable: false,
|
|
16
|
+
configurable: false,
|
|
17
|
+
});
|
|
18
|
+
(0, core_utils_1.defineIfNotExists)(proto, 'locked', () => 'overwritten');
|
|
19
|
+
expect(proto.locked()).toBe('original');
|
|
20
|
+
});
|
|
21
|
+
it('should overwrite a writable property', () => {
|
|
22
|
+
const proto = {};
|
|
23
|
+
Object.defineProperty(proto, 'flexible', {
|
|
24
|
+
value: () => 'original',
|
|
25
|
+
writable: true,
|
|
26
|
+
configurable: false,
|
|
27
|
+
});
|
|
28
|
+
(0, core_utils_1.defineIfNotExists)(proto, 'flexible', () => 'overwritten');
|
|
29
|
+
expect(proto.flexible()).toBe('overwritten');
|
|
30
|
+
});
|
|
31
|
+
it('should overwrite a configurable property', () => {
|
|
32
|
+
const proto = {};
|
|
33
|
+
Object.defineProperty(proto, 'flexible', {
|
|
34
|
+
value: () => 'original',
|
|
35
|
+
writable: false,
|
|
36
|
+
configurable: true,
|
|
37
|
+
});
|
|
38
|
+
(0, core_utils_1.defineIfNotExists)(proto, 'flexible', () => 'overwritten');
|
|
39
|
+
expect(proto.flexible()).toBe('overwritten');
|
|
40
|
+
});
|
|
41
|
+
it('should define the property as non-enumerable', () => {
|
|
42
|
+
const proto = {};
|
|
43
|
+
(0, core_utils_1.defineIfNotExists)(proto, 'hidden', () => 'value');
|
|
44
|
+
expect(Object.keys(proto)).not.toContain('hidden');
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
describe('defineStaticIfNotExists()', () => {
|
|
48
|
+
it('should define a new static method on a constructor', () => {
|
|
49
|
+
const ctor = {};
|
|
50
|
+
(0, core_utils_1.defineStaticIfNotExists)(ctor, 'create', () => 'created');
|
|
51
|
+
expect(ctor.create()).toBe('created');
|
|
52
|
+
});
|
|
53
|
+
it('should not overwrite a non-configurable non-writable static property', () => {
|
|
54
|
+
const ctor = {};
|
|
55
|
+
Object.defineProperty(ctor, 'locked', {
|
|
56
|
+
value: () => 'original',
|
|
57
|
+
writable: false,
|
|
58
|
+
configurable: false,
|
|
59
|
+
});
|
|
60
|
+
(0, core_utils_1.defineStaticIfNotExists)(ctor, 'locked', () => 'overwritten');
|
|
61
|
+
expect(ctor.locked()).toBe('original');
|
|
62
|
+
});
|
|
63
|
+
it('should define the property as non-enumerable', () => {
|
|
64
|
+
const ctor = {};
|
|
65
|
+
(0, core_utils_1.defineStaticIfNotExists)(ctor, 'hidden', () => 'value');
|
|
66
|
+
expect(Object.keys(ctor)).not.toContain('hidden');
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
describe('resolveSelector()', () => {
|
|
70
|
+
describe('with function selector', () => {
|
|
71
|
+
it('should return a function that applies the selector', () => {
|
|
72
|
+
const sel = (0, core_utils_1.resolveSelector)((item) => item.x);
|
|
73
|
+
expect(sel({ x: 42 })).toBe(42);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
describe('with string key selector', () => {
|
|
77
|
+
it('should return a function that extracts the property', () => {
|
|
78
|
+
const sel = (0, core_utils_1.resolveSelector)('name');
|
|
79
|
+
expect(sel({ name: 'Alice' })).toBe('Alice');
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
describe('with fallback', () => {
|
|
83
|
+
it('should return the fallback function when no selector is provided', () => {
|
|
84
|
+
const sel = (0, core_utils_1.resolveSelector)(undefined, (item) => item.toString());
|
|
85
|
+
expect(sel(42)).toBe('42');
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
describe('error handling', () => {
|
|
89
|
+
it('should throw TypeError when selector is invalid', () => {
|
|
90
|
+
expect(() => (0, core_utils_1.resolveSelector)(123)).toThrow(TypeError);
|
|
91
|
+
});
|
|
92
|
+
it('should throw TypeError when no selector and no fallback', () => {
|
|
93
|
+
expect(() => (0, core_utils_1.resolveSelector)(undefined, undefined)).toThrow(TypeError);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
describe('assertValidSelector()', () => {
|
|
98
|
+
it('should not throw when selector is a function', () => {
|
|
99
|
+
expect(() => (0, core_utils_1.assertValidSelector)(() => { })).not.toThrow();
|
|
100
|
+
});
|
|
101
|
+
it('should not throw when selector is a string', () => {
|
|
102
|
+
expect(() => (0, core_utils_1.assertValidSelector)('name')).not.toThrow();
|
|
103
|
+
});
|
|
104
|
+
it('should not throw when selector is undefined', () => {
|
|
105
|
+
expect(() => (0, core_utils_1.assertValidSelector)(undefined)).not.toThrow();
|
|
106
|
+
});
|
|
107
|
+
describe('error handling', () => {
|
|
108
|
+
it('should throw TypeError when selector is a number', () => {
|
|
109
|
+
expect(() => (0, core_utils_1.assertValidSelector)(123)).toThrow(TypeError);
|
|
110
|
+
});
|
|
111
|
+
it('should throw TypeError when selector is an object', () => {
|
|
112
|
+
expect(() => (0, core_utils_1.assertValidSelector)({})).toThrow(TypeError);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
describe('isNumberOrString()', () => {
|
|
117
|
+
it('should return true for a string', () => {
|
|
118
|
+
expect((0, core_utils_1.isNumberOrString)('hello')).toBe(true);
|
|
119
|
+
});
|
|
120
|
+
it('should return true for a number', () => {
|
|
121
|
+
expect((0, core_utils_1.isNumberOrString)(42)).toBe(true);
|
|
122
|
+
});
|
|
123
|
+
it('should return false for a boolean', () => {
|
|
124
|
+
expect((0, core_utils_1.isNumberOrString)(true)).toBe(false);
|
|
125
|
+
});
|
|
126
|
+
it('should return false for null', () => {
|
|
127
|
+
expect((0, core_utils_1.isNumberOrString)(null)).toBe(false);
|
|
128
|
+
});
|
|
129
|
+
it('should return false for an object', () => {
|
|
130
|
+
expect((0, core_utils_1.isNumberOrString)({})).toBe(false);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
describe('utilitishError()', () => {
|
|
134
|
+
it('should throw a TypeError by default', () => {
|
|
135
|
+
expect(() => (0, core_utils_1.utilitishError)('method', 'message')).toThrow(TypeError);
|
|
136
|
+
});
|
|
137
|
+
it('should throw a RangeError when specified', () => {
|
|
138
|
+
expect(() => (0, core_utils_1.utilitishError)('method', 'message', undefined, RangeError)).toThrow(RangeError);
|
|
139
|
+
});
|
|
140
|
+
it('should include method and message in the error message', () => {
|
|
141
|
+
expect(() => (0, core_utils_1.utilitishError)('String.prototype.test', 'value must be a string')).toThrow('[Utilitish] String.prototype.test: value must be a string');
|
|
142
|
+
});
|
|
143
|
+
it('should include received type in the error message when provided', () => {
|
|
144
|
+
expect(() => (0, core_utils_1.utilitishError)('String.prototype.test', 'value must be a string', 42)).toThrow('[Utilitish] String.prototype.test: value must be a string, received number');
|
|
145
|
+
});
|
|
146
|
+
it('should not include received info when received is undefined', () => {
|
|
147
|
+
expect(() => (0, core_utils_1.utilitishError)('String.prototype.test', 'value must be a string', undefined)).toThrow('[Utilitish] String.prototype.test: value must be a string');
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
});
|
|
@@ -6,6 +6,10 @@ function sortBy(arr, direction, selector) {
|
|
|
6
6
|
if (arr.length === 0)
|
|
7
7
|
return arr.slice();
|
|
8
8
|
const getValue = (0, core_utils_1.resolveSelector)(selector, (item) => item);
|
|
9
|
+
const sample = getValue(arr[0]);
|
|
10
|
+
if (!(0, core_utils_1.isNumberOrString)(sample)) {
|
|
11
|
+
throw new TypeError('Selector must return number or string');
|
|
12
|
+
}
|
|
9
13
|
if (!selector && !arr.every((item) => (0, core_utils_1.isNumberOrString)(item))) {
|
|
10
14
|
throw new TypeError('Array elements must be number or string if no selector is provided');
|
|
11
15
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const logic_utils_1 = require("./logic.utils");
|
|
4
|
+
describe('logic.utils', () => {
|
|
5
|
+
describe('sortBy()', () => {
|
|
6
|
+
describe('with primitive arrays', () => {
|
|
7
|
+
it('should sort numbers in ascending order', () => {
|
|
8
|
+
expect((0, logic_utils_1.sortBy)([3, 1, 2], 'asc')).toEqual([1, 2, 3]);
|
|
9
|
+
});
|
|
10
|
+
it('should sort numbers in descending order', () => {
|
|
11
|
+
expect((0, logic_utils_1.sortBy)([1, 3, 2], 'desc')).toEqual([3, 2, 1]);
|
|
12
|
+
});
|
|
13
|
+
it('should sort strings in ascending order', () => {
|
|
14
|
+
expect((0, logic_utils_1.sortBy)(['b', 'a', 'c'], 'asc')).toEqual(['a', 'b', 'c']);
|
|
15
|
+
});
|
|
16
|
+
it('should sort strings in descending order', () => {
|
|
17
|
+
expect((0, logic_utils_1.sortBy)(['b', 'a', 'c'], 'desc')).toEqual(['c', 'b', 'a']);
|
|
18
|
+
});
|
|
19
|
+
it('should return empty array when input is empty', () => {
|
|
20
|
+
expect((0, logic_utils_1.sortBy)([], 'asc')).toEqual([]);
|
|
21
|
+
});
|
|
22
|
+
it('should not mutate the original array', () => {
|
|
23
|
+
const arr = [3, 1, 2];
|
|
24
|
+
(0, logic_utils_1.sortBy)(arr, 'asc');
|
|
25
|
+
expect(arr).toEqual([3, 1, 2]);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
describe('with selector function', () => {
|
|
29
|
+
it('should sort in ascending order using selector', () => {
|
|
30
|
+
const arr = [{ v: 3 }, { v: 1 }, { v: 2 }];
|
|
31
|
+
expect((0, logic_utils_1.sortBy)(arr, 'asc', (x) => x.v)).toEqual([{ v: 1 }, { v: 2 }, { v: 3 }]);
|
|
32
|
+
});
|
|
33
|
+
it('should sort in descending order using selector', () => {
|
|
34
|
+
const arr = [{ v: 1 }, { v: 3 }, { v: 2 }];
|
|
35
|
+
expect((0, logic_utils_1.sortBy)(arr, 'desc', (x) => x.v)).toEqual([{ v: 3 }, { v: 2 }, { v: 1 }]);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
describe('with selector string key', () => {
|
|
39
|
+
it('should sort in ascending order using property key', () => {
|
|
40
|
+
const arr = [{ v: 3 }, { v: 1 }, { v: 2 }];
|
|
41
|
+
expect((0, logic_utils_1.sortBy)(arr, 'asc', 'v')).toEqual([{ v: 1 }, { v: 2 }, { v: 3 }]);
|
|
42
|
+
});
|
|
43
|
+
it('should sort in descending order using property key', () => {
|
|
44
|
+
const arr = [{ v: 1 }, { v: 3 }, { v: 2 }];
|
|
45
|
+
expect((0, logic_utils_1.sortBy)(arr, 'desc', 'v')).toEqual([{ v: 3 }, { v: 2 }, { v: 1 }]);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
describe('error handling', () => {
|
|
49
|
+
it('should throw TypeError when elements are not sortable without selector', () => {
|
|
50
|
+
expect(() => (0, logic_utils_1.sortBy)([{ v: 1 }], 'asc')).toThrow(TypeError);
|
|
51
|
+
});
|
|
52
|
+
it('should throw TypeError when selector returns non-sortable value', () => {
|
|
53
|
+
expect(() => (0, logic_utils_1.sortBy)([{ v: {} }], 'asc', (x) => x.v)).toThrow(TypeError);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -96,3 +96,12 @@ export declare function getSlugifyConfig(): SlugifyConfig;
|
|
|
96
96
|
* Resets the global slugify configuration to default values.
|
|
97
97
|
*/
|
|
98
98
|
export declare function resetSlugifyConfig(): void;
|
|
99
|
+
/**
|
|
100
|
+
* Applies slugify transformations to a string based on the provided configuration.
|
|
101
|
+
* This is the core logic for converting strings to URL-friendly slugs.
|
|
102
|
+
*
|
|
103
|
+
* @param str - The input string to slugify
|
|
104
|
+
* @param config - Configuration object (uses global config if not provided)
|
|
105
|
+
* @returns The slugified string
|
|
106
|
+
*/
|
|
107
|
+
export declare function slugifyString(str: string, config?: SlugifyConfig): string;
|