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.
@@ -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' || source === null) {
15
- throw new TypeError('Source must be a non-null object');
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<T>(): T[];
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
- throw new TypeError('Arguments must be an array');
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
- throw new TypeError('Arguments must be an array or a Set');
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
- throw new TypeError('Arguments must be Sets');
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
- throw new TypeError('Arguments must be Sets');
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} True if both slugified strings are equal, false otherwise
204
- * @throws {TypeError} If the parameter is not a string
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'.compareSlugify('hello-world'); // true
208
- * 'Héllo Wørld'.compareSlugify('hello-world'); // true
209
- * 'Hello World'.compareSlugify('goodbye-world'); // false
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 using the default or global configuration
213
- * - Useful for comparing user-provided strings with stored slugs
214
- * - Guard ensures the parameter is a valid string type
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
- compareSlugify(other: string): boolean;
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 slugify_utils_1 = require("../utils/slugify.utils");
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' || !Number.isInteger(n) || n < 0) {
46
- throw new TypeError('Truncate length must be a non-negative integer');
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, slugify_utils_1.slugifyString)(this, config);
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.compareSlugify
80
+ * @see String.prototype.slugifyEquals
78
81
  */
79
- (0, core_utils_1.defineIfNotExists)(String.prototype, 'compareSlugify', function (other) {
80
- if (typeof other !== 'string') {
81
- throw new TypeError('Parameter must be a string');
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) || !Number.isInteger(end)) {
90
- throw new TypeError('start and end must be integers');
91
- }
92
- if (start < 0 || end < 0) {
93
- throw new RangeError('start or end cannot be negative');
94
- }
95
- if (start > this.length || end > this.length) {
96
- throw new RangeError('start or end is out of bounds');
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('compareSlugify()', () => {
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'.compareSlugify('hello-world')).toBe(true);
170
- expect('hello world'.compareSlugify('hello-world')).toBe(true);
171
- expect('HELLO WORLD'.compareSlugify('hello-world')).toBe(true);
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é'.compareSlugify('cafe')).toBe(true);
175
- expect('Éléphant'.compareSlugify('elephant')).toBe(true);
176
- expect('naïve café'.compareSlugify('naive-cafe')).toBe(true);
177
- expect('résumé'.compareSlugify('resume')).toBe(true);
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'.compareSlugify('goodbye-world')).toBe(false);
181
- expect('test string'.compareSlugify('different-string')).toBe(false);
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 '.compareSlugify('hello-world')).toBe(true);
185
- expect('Hello!!!World???'.compareSlugify('hello-world')).toBe(true);
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'.compareSlugify(123)).toThrowError(TypeError);
189
- expect(() => 'hello'.compareSlugify(null)).toThrowError(TypeError);
190
- expect(() => 'hello'.compareSlugify(undefined)).toThrowError(TypeError);
191
- expect(() => 'hello'.compareSlugify({})).toThrowError(TypeError);
192
- expect(() => 'hello'.compareSlugify([])).toThrowError(TypeError);
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(''.compareSlugify('')).toBe(true);
196
- expect(' '.compareSlugify('')).toBe(true);
197
- expect('hello'.compareSlugify('')).toBe(false);
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
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta
6
+ name="viewport"
7
+ content="width=device-width, initial-scale=1.0"
8
+ />
9
+ <script src="index.js"></script>
10
+ <title>Document</title>
11
+ </head>
12
+ <body></body>
13
+ </html>
@@ -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;
@@ -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;