utilitish 0.0.14 → 0.0.16

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.
@@ -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
@@ -73,19 +76,65 @@ const slugify_utils_1 = require("../utils/slugify.utils");
73
76
  return '';
74
77
  return this.charAt(0).toUpperCase() + this.slice(1);
75
78
  });
79
+ /**
80
+ * @see String.prototype.slugifyEquals
81
+ */
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);
85
+ return this.slugify() === other.slugify();
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
+ });
76
122
  /**
77
123
  * @see String.prototype.replaceRange
78
124
  */
79
125
  (0, core_utils_1.defineIfNotExists)(String.prototype, 'replaceRange', function (start, end, replaceString = '') {
80
- if (!Number.isInteger(start) || !Number.isInteger(end)) {
81
- throw new TypeError('start and end must be integers');
82
- }
83
- if (start < 0 || end < 0) {
84
- throw new RangeError('start or end cannot be negative');
85
- }
86
- if (start > this.length || end > this.length) {
87
- throw new RangeError('start or end is out of bounds');
88
- }
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);
89
138
  if (start > end)
90
139
  [start, end] = [end, start];
91
140
  return this.slice(0, start) + (replaceString ?? '') + this.slice(end);
@@ -161,6 +161,42 @@ describe('String.prototype', () => {
161
161
  });
162
162
  });
163
163
  });
164
+ describe('slugifyEquals()', () => {
165
+ beforeEach(() => {
166
+ (0, slugify_config_1.resetSlugifyConfig)(); // Reset to defaults before each test
167
+ });
168
+ it('should return true when slugified strings are equal', () => {
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
+ });
173
+ it('should handle accents and special characters', () => {
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
+ });
179
+ it('should return false when slugified strings are different', () => {
180
+ expect('Hello World'.slugifyEquals('goodbye-world')).toBe(false);
181
+ expect('test string'.slugifyEquals('different-string')).toBe(false);
182
+ });
183
+ it('should handle extra spaces and special characters', () => {
184
+ expect(' Hello --- World '.slugifyEquals('hello-world')).toBe(true);
185
+ expect('Hello!!!World???'.slugifyEquals('hello-world')).toBe(true);
186
+ });
187
+ it('should throw TypeError if parameter is not a string', () => {
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
+ });
194
+ it('should work with empty strings', () => {
195
+ expect(''.slugifyEquals('')).toBe(true);
196
+ expect(' '.slugifyEquals('')).toBe(true);
197
+ expect('hello'.slugifyEquals('')).toBe(false);
198
+ });
199
+ });
164
200
  describe('replaceRange()', () => {
165
201
  it('should replace a single character at the given index', () => {
166
202
  expect('hello'.replaceRange(1, 2, 'a')).toBe('hallo');
@@ -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
+ });
@@ -10,7 +10,6 @@ export declare class SlugifyConfig {
10
10
  private readonly _allowedChars?;
11
11
  private readonly _maxLength?;
12
12
  private readonly _transformers;
13
- private readonly _isValidated;
14
13
  private constructor();
15
14
  static builder(): SlugifyConfigBuilder;
16
15
  static default(): SlugifyConfig;
@@ -84,7 +83,7 @@ export declare function getDefaultSlugifyConfig(): SlugifyConfig;
84
83
  * );
85
84
  */
86
85
  export declare function assertCharClass(regex: RegExp): void;
87
- export declare function validateSlugifyConfig(builderOrConfig: SlugifyConfigBuilder | SlugifyConfig): void;
86
+ export declare function assertSlugifyConfig(builderOrConfig: SlugifyConfigBuilder | SlugifyConfig): void;
88
87
  export declare function setSlugifyConfig(config: SlugifyConfig): void;
89
88
  /**
90
89
  * Gets the current global slugify configuration.
@@ -97,3 +96,12 @@ export declare function getSlugifyConfig(): SlugifyConfig;
97
96
  * Resets the global slugify configuration to default values.
98
97
  */
99
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;
@@ -3,16 +3,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SlugifyConfigBuilder = exports.SlugifyConfig = void 0;
4
4
  exports.getDefaultSlugifyConfig = getDefaultSlugifyConfig;
5
5
  exports.assertCharClass = assertCharClass;
6
- exports.validateSlugifyConfig = validateSlugifyConfig;
6
+ exports.assertSlugifyConfig = assertSlugifyConfig;
7
7
  exports.setSlugifyConfig = setSlugifyConfig;
8
8
  exports.getSlugifyConfig = getSlugifyConfig;
9
9
  exports.resetSlugifyConfig = resetSlugifyConfig;
10
+ exports.slugifyString = slugifyString;
10
11
  /**
11
12
  * Configuration class for the slugify functionality.
12
13
  * Provides comprehensive customization options for URL slug generation.
13
14
  */
14
15
  class SlugifyConfig {
15
- constructor(builder, isValidated = false) {
16
+ constructor(builder) {
16
17
  this._customReplacements = builder.customReplacements;
17
18
  this._separator = builder.separator;
18
19
  this._case = builder.case;
@@ -20,7 +21,6 @@ class SlugifyConfig {
20
21
  this._allowedChars = builder.allowedChars;
21
22
  this._maxLength = builder.maxLength;
22
23
  this._transformers = builder.transformers;
23
- this._isValidated = isValidated;
24
24
  }
25
25
  static builder() {
26
26
  return new SlugifyConfigBuilder();
@@ -29,8 +29,8 @@ class SlugifyConfig {
29
29
  return SlugifyConfig.builder().build();
30
30
  }
31
31
  static create(builder) {
32
- validateSlugifyConfig(builder);
33
- const config = new SlugifyConfig(builder, true);
32
+ assertSlugifyConfig(builder);
33
+ const config = new SlugifyConfig(builder);
34
34
  return Object.freeze(config);
35
35
  }
36
36
  get customReplacements() {
@@ -217,7 +217,7 @@ function assertCharClass(regex) {
217
217
  throw new Error(`Invalid allowedChars: must be a character class like /[a-z]/, received ${regex}`);
218
218
  }
219
219
  }
220
- function validateSlugifyConfig(builderOrConfig) {
220
+ function assertSlugifyConfig(builderOrConfig) {
221
221
  const allowedChars = builderOrConfig.allowedChars;
222
222
  const separator = builderOrConfig.separator;
223
223
  const caseValue = builderOrConfig.case;
@@ -270,3 +270,17 @@ function getSlugifyConfig() {
270
270
  function resetSlugifyConfig() {
271
271
  globalSlugifyConfig = getDefaultSlugifyConfig();
272
272
  }
273
+ /**
274
+ * Applies slugify transformations to a string based on the provided configuration.
275
+ * This is the core logic for converting strings to URL-friendly slugs.
276
+ *
277
+ * @param str - The input string to slugify
278
+ * @param config - Configuration object (uses global config if not provided)
279
+ * @returns The slugified string
280
+ */
281
+ function slugifyString(str, config) {
282
+ // Merge provided config with global config
283
+ const globalConfig = getSlugifyConfig();
284
+ const finalConfig = config ? globalConfig.merge(config) : globalConfig;
285
+ return finalConfig.slugify(str);
286
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,144 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const slugify_config_1 = require("./slugify.config");
4
+ describe('slugify.config', () => {
5
+ afterEach(() => {
6
+ (0, slugify_config_1.resetSlugifyConfig)();
7
+ });
8
+ describe('SlugifyConfig.default()', () => {
9
+ it('should return a config with default separator', () => {
10
+ expect((0, slugify_config_1.getDefaultSlugifyConfig)().separator).toBe('-');
11
+ });
12
+ it('should return a config with lowercase case', () => {
13
+ expect((0, slugify_config_1.getDefaultSlugifyConfig)().case).toBe('lower');
14
+ });
15
+ it('should return a config with removeAccents enabled', () => {
16
+ expect((0, slugify_config_1.getDefaultSlugifyConfig)().removeAccents).toBe(true);
17
+ });
18
+ });
19
+ describe('SlugifyConfig.slugify()', () => {
20
+ describe('with default config', () => {
21
+ it('should convert to lowercase', () => {
22
+ expect(slugify_config_1.SlugifyConfig.default().slugify('Hello World')).toBe('hello-world');
23
+ });
24
+ it('should replace spaces with separator', () => {
25
+ expect(slugify_config_1.SlugifyConfig.default().slugify('foo bar')).toBe('foo-bar');
26
+ });
27
+ it('should remove accents', () => {
28
+ expect(slugify_config_1.SlugifyConfig.default().slugify('Héllo')).toBe('hello');
29
+ });
30
+ it('should collapse multiple separators', () => {
31
+ expect(slugify_config_1.SlugifyConfig.default().slugify('foo bar')).toBe('foo-bar');
32
+ });
33
+ it('should trim leading and trailing separators', () => {
34
+ expect(slugify_config_1.SlugifyConfig.default().slugify(' foo ')).toBe('foo');
35
+ });
36
+ });
37
+ describe('with custom separator', () => {
38
+ it('should use custom separator', () => {
39
+ const config = slugify_config_1.SlugifyConfig.builder().withSeparator('_').build();
40
+ expect(config.slugify('hello world')).toBe('hello_world');
41
+ });
42
+ });
43
+ describe('with maxLength', () => {
44
+ it('should truncate to maxLength', () => {
45
+ const config = slugify_config_1.SlugifyConfig.builder().withMaxLength(5).build();
46
+ expect(config.slugify('hello world').length).toBeLessThanOrEqual(5);
47
+ });
48
+ });
49
+ describe('with custom replacements', () => {
50
+ it('should apply custom replacements before slugifying', () => {
51
+ const config = slugify_config_1.SlugifyConfig.builder().withCustomReplacements({ '&': 'and' }).build();
52
+ expect(config.slugify('cats & dogs')).toBe('cats-and-dogs');
53
+ });
54
+ });
55
+ describe('with transformers', () => {
56
+ it('should apply transformers before slugifying', () => {
57
+ const config = slugify_config_1.SlugifyConfig.builder()
58
+ .withTransformers([(s) => s.replace('foo', 'bar')])
59
+ .build();
60
+ expect(config.slugify('foo baz')).toBe('bar-baz');
61
+ });
62
+ });
63
+ });
64
+ describe('SlugifyConfig.slugifyWith()', () => {
65
+ it('should slugify with a temporary configuration', () => {
66
+ const result = slugify_config_1.SlugifyConfig.slugifyWith('hello world', (b) => b.withSeparator('_'));
67
+ expect(result).toBe('hello_world');
68
+ });
69
+ });
70
+ describe('SlugifyConfig.merge()', () => {
71
+ it('should merge two configs with the other taking precedence', () => {
72
+ const base = slugify_config_1.SlugifyConfig.builder().withSeparator('-').build();
73
+ const other = slugify_config_1.SlugifyConfig.builder().withSeparator('_').build();
74
+ expect(base.merge(other).separator).toBe('_');
75
+ });
76
+ });
77
+ describe('assertCharClass()', () => {
78
+ it('should not throw for a valid character class', () => {
79
+ expect(() => (0, slugify_config_1.assertCharClass)(/[a-z]/)).not.toThrow();
80
+ });
81
+ describe('error handling', () => {
82
+ it('should throw Error for a regex that is not a character class', () => {
83
+ expect(() => (0, slugify_config_1.assertCharClass)(/a-z/)).toThrow(Error);
84
+ });
85
+ });
86
+ });
87
+ describe('assertSlugifyConfig()', () => {
88
+ it('should not throw for a valid builder', () => {
89
+ expect(() => (0, slugify_config_1.assertSlugifyConfig)(slugify_config_1.SlugifyConfig.builder())).not.toThrow();
90
+ });
91
+ describe('error handling', () => {
92
+ it('should throw TypeError for invalid separator', () => {
93
+ const builder = slugify_config_1.SlugifyConfig.builder();
94
+ builder.separator = 123;
95
+ expect(() => (0, slugify_config_1.assertSlugifyConfig)(builder)).toThrow(TypeError);
96
+ });
97
+ it('should throw TypeError for invalid case value', () => {
98
+ const builder = slugify_config_1.SlugifyConfig.builder();
99
+ builder.case = 'invalid';
100
+ expect(() => (0, slugify_config_1.assertSlugifyConfig)(builder)).toThrow(TypeError);
101
+ });
102
+ it('should throw TypeError for invalid removeAccents', () => {
103
+ const builder = slugify_config_1.SlugifyConfig.builder();
104
+ builder.removeAccents = 'yes';
105
+ expect(() => (0, slugify_config_1.assertSlugifyConfig)(builder)).toThrow(TypeError);
106
+ });
107
+ it('should throw TypeError for invalid maxLength', () => {
108
+ const builder = slugify_config_1.SlugifyConfig.builder();
109
+ builder.maxLength = -1;
110
+ expect(() => (0, slugify_config_1.assertSlugifyConfig)(builder)).toThrow(TypeError);
111
+ });
112
+ it('should throw TypeError for invalid allowedChars', () => {
113
+ const builder = slugify_config_1.SlugifyConfig.builder();
114
+ builder.allowedChars = 'not-a-regex';
115
+ expect(() => (0, slugify_config_1.assertSlugifyConfig)(builder)).toThrow(TypeError);
116
+ });
117
+ it('should throw TypeError for invalid transformers', () => {
118
+ const builder = slugify_config_1.SlugifyConfig.builder();
119
+ builder.transformers = ['not-a-function'];
120
+ expect(() => (0, slugify_config_1.assertSlugifyConfig)(builder)).toThrow(TypeError);
121
+ });
122
+ });
123
+ });
124
+ describe('getSlugifyConfig()', () => {
125
+ it('should return the default config initially', () => {
126
+ expect((0, slugify_config_1.getSlugifyConfig)().separator).toBe('-');
127
+ });
128
+ });
129
+ describe('setSlugifyConfig()', () => {
130
+ it('should update the global config', () => {
131
+ const config = slugify_config_1.SlugifyConfig.builder().withSeparator('_').build();
132
+ (0, slugify_config_1.setSlugifyConfig)(config);
133
+ expect((0, slugify_config_1.getSlugifyConfig)().separator).toBe('_');
134
+ });
135
+ });
136
+ describe('resetSlugifyConfig()', () => {
137
+ it('should restore the default config', () => {
138
+ const config = slugify_config_1.SlugifyConfig.builder().withSeparator('_').build();
139
+ (0, slugify_config_1.setSlugifyConfig)(config);
140
+ (0, slugify_config_1.resetSlugifyConfig)();
141
+ expect((0, slugify_config_1.getSlugifyConfig)().separator).toBe('-');
142
+ });
143
+ });
144
+ });