js-powerkit 1.0.0
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 +21 -0
- package/README.md +519 -0
- package/package.json +48 -0
- package/src/arrayUtils.js +347 -0
- package/src/index.js +8 -0
- package/src/objectUtils.js +423 -0
- package/src/stringUtils.js +319 -0
- package/tests/arrayUtils.test.js +204 -0
- package/tests/objectUtils.test.js +211 -0
- package/tests/stringUtils.test.js +220 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import {
|
|
2
|
+
deepClone,
|
|
3
|
+
deepMerge,
|
|
4
|
+
pick,
|
|
5
|
+
omit,
|
|
6
|
+
isEmpty,
|
|
7
|
+
invert,
|
|
8
|
+
mapKeys,
|
|
9
|
+
mapValues,
|
|
10
|
+
defaults,
|
|
11
|
+
getNestedValue,
|
|
12
|
+
setNestedValue,
|
|
13
|
+
getAllKeys,
|
|
14
|
+
flattenObject,
|
|
15
|
+
unflattenObject,
|
|
16
|
+
removeNullish,
|
|
17
|
+
isEqual,
|
|
18
|
+
filterObject,
|
|
19
|
+
toQueryString,
|
|
20
|
+
fromQueryString,
|
|
21
|
+
size,
|
|
22
|
+
has,
|
|
23
|
+
hasPath
|
|
24
|
+
} from '../src/objectUtils.js';
|
|
25
|
+
|
|
26
|
+
describe('Object Utilities', () => {
|
|
27
|
+
describe('deepClone', () => {
|
|
28
|
+
test('deep clones objects', () => {
|
|
29
|
+
const obj = { a: 1, b: { c: 2 } };
|
|
30
|
+
const cloned = deepClone(obj);
|
|
31
|
+
expect(cloned).toEqual(obj);
|
|
32
|
+
expect(cloned).not.toBe(obj);
|
|
33
|
+
expect(cloned.b).not.toBe(obj.b);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('handles primitives', () => {
|
|
37
|
+
expect(deepClone(42)).toBe(42);
|
|
38
|
+
expect(deepClone('hello')).toBe('hello');
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('deepMerge', () => {
|
|
43
|
+
test('merges objects deeply', () => {
|
|
44
|
+
const obj1 = { a: 1, b: { c: 2 } };
|
|
45
|
+
const obj2 = { b: { d: 3 }, e: 4 };
|
|
46
|
+
expect(deepMerge(obj1, obj2)).toEqual({
|
|
47
|
+
a: 1,
|
|
48
|
+
b: { c: 2, d: 3 },
|
|
49
|
+
e: 4
|
|
50
|
+
});
|
|
51
|
+
expect(deepMerge({ a: 1 }, { b: 2 })).toEqual({ a: 1, b: 2 });
|
|
52
|
+
expect(deepMerge({ a: { b: 1 } }, { a: { c: 2 } })).toEqual({ a: { b: 1, c: 2 } });
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('pick', () => {
|
|
57
|
+
test('picks properties', () => {
|
|
58
|
+
expect(pick({ a: 1, b: 2, c: 3 }, ['a', 'c'])).toEqual({ a: 1, c: 3 });
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('throws error for invalid inputs', () => {
|
|
62
|
+
expect(() => pick(null, ['a'])).toThrow(TypeError);
|
|
63
|
+
expect(() => pick({}, 'not array')).toThrow(TypeError);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('omit', () => {
|
|
68
|
+
test('omits properties', () => {
|
|
69
|
+
expect(omit({ a: 1, b: 2, c: 3 }, ['b'])).toEqual({ a: 1, c: 3 });
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('isEmpty', () => {
|
|
74
|
+
test('checks if empty', () => {
|
|
75
|
+
expect(isEmpty({})).toBe(true);
|
|
76
|
+
expect(isEmpty({ a: 1 })).toBe(false);
|
|
77
|
+
expect(isEmpty(null)).toBe(false);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('invert', () => {
|
|
82
|
+
test('inverts keys and values', () => {
|
|
83
|
+
expect(invert({ a: 1, b: 2 })).toEqual({ 1: 'a', 2: 'b' });
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe('mapKeys', () => {
|
|
88
|
+
test('maps keys', () => {
|
|
89
|
+
expect(mapKeys({ a: 1, b: 2 }, key => key.toUpperCase())).toEqual({ A: 1, B: 2 });
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('mapValues', () => {
|
|
94
|
+
test('maps values', () => {
|
|
95
|
+
expect(mapValues({ a: 1, b: 2 }, val => val * 2)).toEqual({ a: 2, b: 4 });
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe('defaults', () => {
|
|
100
|
+
test('sets defaults', () => {
|
|
101
|
+
expect(defaults({ a: 1 }, { a: 2, b: 3 })).toEqual({ a: 1, b: 3 });
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe('getNestedValue', () => {
|
|
106
|
+
test('gets nested value', () => {
|
|
107
|
+
const obj = { a: { b: { c: 42 } } };
|
|
108
|
+
expect(getNestedValue(obj, 'a.b.c')).toBe(42);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('returns default value when not found', () => {
|
|
112
|
+
const obj = { a: { b: 1 } };
|
|
113
|
+
expect(getNestedValue(obj, 'a.c', 'default')).toBe('default');
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('setNestedValue', () => {
|
|
118
|
+
test('sets nested value', () => {
|
|
119
|
+
const obj = { a: { b: 1 } };
|
|
120
|
+
setNestedValue(obj, 'a.c.d', 42);
|
|
121
|
+
expect(obj.a.c.d).toBe(42);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('getAllKeys', () => {
|
|
126
|
+
test('gets all nested keys', () => {
|
|
127
|
+
const obj = { a: 1, b: { c: 2, d: { e: 3 } } };
|
|
128
|
+
expect(getAllKeys(obj)).toEqual(['a', 'b.c', 'b.d.e']);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe('flattenObject', () => {
|
|
133
|
+
test('flattens nested object', () => {
|
|
134
|
+
const obj = { a: 1, b: { c: 2, d: { e: 3 } } };
|
|
135
|
+
expect(flattenObject(obj)).toEqual({
|
|
136
|
+
'a': 1,
|
|
137
|
+
'b.c': 2,
|
|
138
|
+
'b.d.e': 3
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe('unflattenObject', () => {
|
|
144
|
+
test('unflattens object', () => {
|
|
145
|
+
const obj = { 'a': 1, 'b.c': 2, 'b.d.e': 3 };
|
|
146
|
+
expect(unflattenObject(obj)).toEqual({
|
|
147
|
+
a: 1,
|
|
148
|
+
b: { c: 2, d: { e: 3 } }
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe('removeNullish', () => {
|
|
154
|
+
test('removes null and undefined values', () => {
|
|
155
|
+
const obj = { a: 1, b: null, c: undefined, d: 0, e: '' };
|
|
156
|
+
expect(removeNullish(obj)).toEqual({ a: 1, d: 0, e: '' });
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe('isEqual', () => {
|
|
161
|
+
test('checks deep equality', () => {
|
|
162
|
+
expect(isEqual({ a: 1, b: { c: 2 } }, { a: 1, b: { c: 2 } })).toBe(true);
|
|
163
|
+
expect(isEqual({ a: 1 }, { a: 2 })).toBe(false);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe('filterObject', () => {
|
|
168
|
+
test('filters object by predicate', () => {
|
|
169
|
+
const obj = { a: 1, b: 2, c: 3, d: 4 };
|
|
170
|
+
expect(filterObject(obj, val => val % 2 === 0)).toEqual({ b: 2, d: 4 });
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe('toQueryString', () => {
|
|
175
|
+
test('converts object to query string', () => {
|
|
176
|
+
const obj = { name: 'John', age: 30 };
|
|
177
|
+
expect(toQueryString(obj)).toBe('name=John&age=30');
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe('fromQueryString', () => {
|
|
182
|
+
test('converts query string to object', () => {
|
|
183
|
+
expect(fromQueryString('name=John&age=30')).toEqual({
|
|
184
|
+
name: 'John',
|
|
185
|
+
age: '30'
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe('size', () => {
|
|
191
|
+
test('returns object size', () => {
|
|
192
|
+
expect(size({ a: 1, b: 2, c: 3 })).toBe(3);
|
|
193
|
+
expect(size({})).toBe(0);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe('has', () => {
|
|
198
|
+
test('checks property existence', () => {
|
|
199
|
+
expect(has({ a: 1 }, 'a')).toBe(true);
|
|
200
|
+
expect(has({ a: 1 }, 'b')).toBe(false);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
describe('hasPath', () => {
|
|
205
|
+
test('checks if path exists', () => {
|
|
206
|
+
const obj = { a: { b: { c: 1 } } };
|
|
207
|
+
expect(hasPath(obj, 'a.b.c')).toBe(true);
|
|
208
|
+
expect(hasPath(obj, 'a.b.d')).toBe(false);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
});
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import {
|
|
2
|
+
capitalize,
|
|
3
|
+
toCamelCase,
|
|
4
|
+
toKebabCase,
|
|
5
|
+
toSnakeCase,
|
|
6
|
+
toPascalCase,
|
|
7
|
+
toTitleCase,
|
|
8
|
+
reverse,
|
|
9
|
+
truncate,
|
|
10
|
+
isPalindrome,
|
|
11
|
+
mask,
|
|
12
|
+
slugify,
|
|
13
|
+
countWords,
|
|
14
|
+
removeDuplicates,
|
|
15
|
+
removeWhitespace,
|
|
16
|
+
extractEmails,
|
|
17
|
+
extractUrls,
|
|
18
|
+
stripHtml,
|
|
19
|
+
escapeHtml,
|
|
20
|
+
isNumeric,
|
|
21
|
+
isEmail,
|
|
22
|
+
isUrl,
|
|
23
|
+
repeatString
|
|
24
|
+
} from '../src/stringUtils.js';
|
|
25
|
+
|
|
26
|
+
describe('String Utilities', () => {
|
|
27
|
+
describe('capitalize', () => {
|
|
28
|
+
test('capitalizes the first letter', () => {
|
|
29
|
+
expect(capitalize('hello')).toBe('Hello');
|
|
30
|
+
expect(capitalize('world')).toBe('World');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('handles empty string', () => {
|
|
34
|
+
expect(capitalize('')).toBe('');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('throws error for non-string input', () => {
|
|
38
|
+
expect(() => capitalize(123)).toThrow(TypeError);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('toCamelCase', () => {
|
|
43
|
+
test('converts to camelCase', () => {
|
|
44
|
+
expect(toCamelCase('hello world')).toBe('helloWorld');
|
|
45
|
+
expect(toCamelCase('foo-bar')).toBe('fooBar');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('throws error for non-string input', () => {
|
|
49
|
+
expect(() => toCamelCase(null)).toThrow(TypeError);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('toKebabCase', () => {
|
|
54
|
+
test('converts to kebab-case', () => {
|
|
55
|
+
expect(toKebabCase('Hello World')).toBe('hello-world');
|
|
56
|
+
expect(toKebabCase('fooBar')).toBe('foo-bar');
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe('toSnakeCase', () => {
|
|
61
|
+
test('converts to snake_case', () => {
|
|
62
|
+
expect(toSnakeCase('Hello World')).toBe('hello_world');
|
|
63
|
+
expect(toSnakeCase('fooBar')).toBe('foo_bar');
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('toPascalCase', () => {
|
|
68
|
+
test('converts to PascalCase', () => {
|
|
69
|
+
expect(toPascalCase('hello world')).toBe('HelloWorld');
|
|
70
|
+
expect(toPascalCase('foo-bar')).toBe('FooBar');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('toTitleCase', () => {
|
|
75
|
+
test('converts to title case', () => {
|
|
76
|
+
expect(toTitleCase('hello world')).toBe('Hello World');
|
|
77
|
+
expect(toTitleCase('HELLO WORLD')).toBe('Hello World');
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('reverse', () => {
|
|
82
|
+
test('reverses the string', () => {
|
|
83
|
+
expect(reverse('hello')).toBe('olleh');
|
|
84
|
+
expect(reverse('123')).toBe('321');
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('truncate', () => {
|
|
89
|
+
describe('Truncation WITH Suffix (default behavior)', () => {
|
|
90
|
+
test('truncates string with default suffix', () => {
|
|
91
|
+
expect(truncate('Hello World', 5)).toBe('He...');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('truncates with custom suffix', () => {
|
|
95
|
+
expect(truncate('Hello World', 5, '***')).toBe('He***');
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('does not truncate if length is sufficient', () => {
|
|
99
|
+
expect(truncate('Hi', 5)).toBe('Hi');
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('Truncation WITHOUT Suffix', () => {
|
|
104
|
+
test('truncates string without suffix when addSuffix is false', () => {
|
|
105
|
+
expect(truncate('Hello World', 5, '', false)).toBe('Hello');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('should ignore suffix parameter when addSuffix is false', () => {
|
|
109
|
+
expect(truncate('Hello World', 8, '!!!', false)).toBe('Hello Wo');
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('isPalindrome', () => {
|
|
115
|
+
test('checks if string is palindrome', () => {
|
|
116
|
+
expect(isPalindrome('racecar')).toBe(true);
|
|
117
|
+
expect(isPalindrome('hello')).toBe(false);
|
|
118
|
+
expect(isPalindrome('A man a plan a canal Panama')).toBe(true);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('mask', () => {
|
|
123
|
+
test('masks string', () => {
|
|
124
|
+
expect(mask('1234567890', 2)).toBe('12******90');
|
|
125
|
+
expect(mask('secret', 1)).toBe('s****t');
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe('slugify', () => {
|
|
130
|
+
test('converts to slug', () => {
|
|
131
|
+
expect(slugify('Hello World!')).toBe('hello-world');
|
|
132
|
+
expect(slugify('Foo & Bar')).toBe('foo-bar');
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe('countWords', () => {
|
|
137
|
+
test('counts words', () => {
|
|
138
|
+
expect(countWords('Hello world')).toBe(2);
|
|
139
|
+
expect(countWords('One')).toBe(1);
|
|
140
|
+
expect(countWords('')).toBe(0);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe('removeDuplicates', () => {
|
|
145
|
+
test('removes duplicate characters', () => {
|
|
146
|
+
expect(removeDuplicates('hello')).toBe('helo');
|
|
147
|
+
expect(removeDuplicates('aaa')).toBe('a');
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe('removeWhitespace', () => {
|
|
152
|
+
test('removes all whitespace', () => {
|
|
153
|
+
expect(removeWhitespace('hello world')).toBe('helloworld');
|
|
154
|
+
expect(removeWhitespace(' hello world ')).toBe('helloworld');
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe('extractEmails', () => {
|
|
159
|
+
test('extracts email addresses', () => {
|
|
160
|
+
const text = 'Contact us at test@example.com or support@test.org';
|
|
161
|
+
expect(extractEmails(text)).toEqual(['test@example.com', 'support@test.org']);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('returns empty array when no emails', () => {
|
|
165
|
+
expect(extractEmails('No emails here')).toEqual([]);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe('extractUrls', () => {
|
|
170
|
+
test('extracts URLs', () => {
|
|
171
|
+
const text = 'Visit https://example.com or http://test.org';
|
|
172
|
+
expect(extractUrls(text)).toEqual(['https://example.com', 'http://test.org']);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
describe('stripHtml', () => {
|
|
177
|
+
test('removes HTML tags', () => {
|
|
178
|
+
expect(stripHtml('<p>Hello <b>World</b></p>')).toBe('Hello World');
|
|
179
|
+
expect(stripHtml('No tags here')).toBe('No tags here');
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe('escapeHtml', () => {
|
|
184
|
+
test('escapes HTML special characters', () => {
|
|
185
|
+
expect(escapeHtml('<div>Test & "quotes"</div>'))
|
|
186
|
+
.toBe('<div>Test & "quotes"</div>');
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
describe('isNumeric', () => {
|
|
191
|
+
test('checks if string is numeric', () => {
|
|
192
|
+
expect(isNumeric('12345')).toBe(true);
|
|
193
|
+
expect(isNumeric('123.45')).toBe(false);
|
|
194
|
+
expect(isNumeric('abc')).toBe(false);
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
describe('isEmail', () => {
|
|
199
|
+
test('validates email format', () => {
|
|
200
|
+
expect(isEmail('test@example.com')).toBe(true);
|
|
201
|
+
expect(isEmail('invalid.email')).toBe(false);
|
|
202
|
+
expect(isEmail('')).toBe(false);
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
describe('isUrl', () => {
|
|
207
|
+
test('validates URL format', () => {
|
|
208
|
+
expect(isUrl('https://example.com')).toBe(true);
|
|
209
|
+
expect(isUrl('http://test.org')).toBe(true);
|
|
210
|
+
expect(isUrl('not a url')).toBe(false);
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
describe('repeatString', () => {
|
|
215
|
+
test('repeats string n times', () => {
|
|
216
|
+
expect(repeatString('ab', 3)).toBe('ababab');
|
|
217
|
+
expect(repeatString('x', 0)).toBe('');
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
});
|