@vpmedia/simplify 1.52.0 โ 1.53.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/CHANGELOG.md +21 -0
- package/README.md +1 -1
- package/package.json +9 -9
- package/src/util/addLeadingZero.js +4 -4
- package/src/util/capitalize.js +2 -2
- package/src/util/capitalize.test.js +27 -3
- package/src/util/deepMerge.test.js +78 -0
- package/src/util/deg2rad.test.js +1 -1
- package/src/util/delayPromise.test.js +21 -0
- package/src/util/fixFloatPrecision.js +8 -0
- package/src/util/fixFloatPrecision.test.js +24 -2
- package/src/util/getObjValueByPath.js +7 -4
- package/src/util/getObjValueByPath.test.js +48 -3
- package/src/util/getRandomInt.test.js +21 -2
- package/src/util/getURLParam.js +5 -5
- package/src/util/getURLParam.test.js +15 -2
- package/src/util/purgeObject.test.js +1 -1
- package/src/util/sanitizeURLParam.test.js +27 -3
- package/src/util/setObjValueByPath.js +10 -5
- package/src/util/setObjValueByPath.test.js +43 -5
- package/src/util/underscoreToCamelCase.test.js +1 -1
- package/types/util/fixFloatPrecision.d.ts.map +1 -1
- package/types/util/getObjValueByPath.d.ts.map +1 -1
- package/types/util/setObjValueByPath.d.ts.map +1 -1
- package/.oxlintrc.json +0 -72
- package/commitlint.config.js +0 -117
- package/eslint.config.js +0 -62
- package/lefthook.yml +0 -26
- package/pnpm-workspace.yaml +0 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
## [1.53.0] - 2025-12-17
|
|
2
|
+
|
|
3
|
+
### ๐ผ Other
|
|
4
|
+
|
|
5
|
+
- Update npmignore
|
|
6
|
+
- *(deps)* Bump dependency versions
|
|
7
|
+
- Update npmignore
|
|
8
|
+
- *(deps)* Bump dependency versions
|
|
9
|
+
|
|
10
|
+
### ๐ Documentation
|
|
11
|
+
|
|
12
|
+
- Improve jsdoc comments
|
|
13
|
+
|
|
14
|
+
### ๐งช Testing
|
|
15
|
+
|
|
16
|
+
- Improve test coverage
|
|
17
|
+
|
|
18
|
+
### โ๏ธ Miscellaneous Tasks
|
|
19
|
+
|
|
20
|
+
- Release
|
|
21
|
+
- *(release)* V1.53.0
|
|
1
22
|
## [1.52.0] - 2025-12-16
|
|
2
23
|
|
|
3
24
|
### ๐ผ Other
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://badge.fury.io/js/@vpmedia%2Fsimplify)
|
|
4
4
|
[](https://github.com/vpmedia/simplify/actions/workflows/ci.yml)
|
|
5
5
|
|
|
6
|
-
@vpmedia/simplify
|
|
6
|
+
@vpmedia/simplify is a utility library for common JavaScript operations.
|
|
7
7
|
|
|
8
8
|
## Getting started
|
|
9
9
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vpmedia/simplify",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.53.0",
|
|
4
4
|
"description": "@vpmedia/simplify",
|
|
5
5
|
"author": "Andras Csizmadia <andras@vpmedia.hu> (www.vpmedia.hu)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -22,26 +22,26 @@
|
|
|
22
22
|
"eventemitter3": "^5.0.1"
|
|
23
23
|
},
|
|
24
24
|
"optionalDependencies": {
|
|
25
|
-
"@sentry/browser": "^10.
|
|
25
|
+
"@sentry/browser": "^10.31.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@commitlint/cli": "^20.2.0",
|
|
29
29
|
"@commitlint/config-conventional": "^20.2.0",
|
|
30
30
|
"@eslint/js": "^9.39.2",
|
|
31
|
-
"@types/node": "^25.0.
|
|
32
|
-
"@vitest/coverage-v8": "^4.0.
|
|
31
|
+
"@types/node": "^25.0.3",
|
|
32
|
+
"@vitest/coverage-v8": "^4.0.16",
|
|
33
33
|
"eslint": "^9.39.2",
|
|
34
34
|
"eslint-plugin-jsdoc": "^61.5.0",
|
|
35
|
-
"eslint-plugin-oxlint": "^1.
|
|
35
|
+
"eslint-plugin-oxlint": "^1.33.0",
|
|
36
36
|
"eslint-plugin-unicorn": "^62.0.0",
|
|
37
37
|
"globals": "^16.5.0",
|
|
38
38
|
"jsdom": "^27.3.0",
|
|
39
|
-
"oxlint": "^1.
|
|
40
|
-
"oxlint-tsgolint": "^0.
|
|
39
|
+
"oxlint": "^1.33.0",
|
|
40
|
+
"oxlint-tsgolint": "^0.9.1",
|
|
41
41
|
"prettier": "^3.7.4",
|
|
42
42
|
"typescript": "^5.9.3",
|
|
43
|
-
"typescript-eslint": "^8.
|
|
44
|
-
"vitest": "^4.0.
|
|
43
|
+
"typescript-eslint": "^8.50.0",
|
|
44
|
+
"vitest": "^4.0.16"
|
|
45
45
|
},
|
|
46
46
|
"browserslist": [
|
|
47
47
|
"> 0.5%",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* @param {number | string | null | undefined} value -
|
|
4
|
-
* @param {number} size -
|
|
5
|
-
* @returns {string | null}
|
|
2
|
+
* Add leading zeros to a value to ensure it has a minimum width.
|
|
3
|
+
* @param {number | string | null | undefined} value - The value to pad with leading zeros.
|
|
4
|
+
* @param {number} size - The minimum width of the resulting string.
|
|
5
|
+
* @returns {string | null} The value padded with leading zeros or null if the input is null/undefined.
|
|
6
6
|
*/
|
|
7
7
|
export const addLeadingZero = (value, size = 2) => {
|
|
8
8
|
if (value === null || value === undefined) {
|
package/src/util/capitalize.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Capitalize a string.
|
|
3
|
-
* @param {string | null | undefined} value -
|
|
4
|
-
* @returns {string | null}
|
|
3
|
+
* @param {string | null | undefined} value - The input string to capitalize.
|
|
4
|
+
* @returns {string | null} The capitalized string or null if the input is null/undefined.
|
|
5
5
|
*/
|
|
6
6
|
export const capitalize = (value) => {
|
|
7
7
|
if (value === null || value === undefined) {
|
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
import { capitalize } from './capitalize.js';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
describe('capitalize', () => {
|
|
4
|
+
test('Capitalizes first letter of string', () => {
|
|
5
|
+
expect(capitalize('test')).toBe('Test');
|
|
6
|
+
expect(capitalize('TEST')).toBe('Test');
|
|
7
|
+
expect(capitalize('tEST')).toBe('Test');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test('Handles null input', () => {
|
|
11
|
+
expect(capitalize(null)).toBe(null);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test('Handles empty string', () => {
|
|
15
|
+
expect(capitalize('')).toBe('');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('Handles empty string with whitespace', () => {
|
|
19
|
+
expect(capitalize(' ')).toBe(' ');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('Handles single character', () => {
|
|
23
|
+
expect(capitalize('a')).toBe('A');
|
|
24
|
+
expect(capitalize('A')).toBe('A');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('Handles strings with numbers', () => {
|
|
28
|
+
expect(capitalize('test123')).toBe('Test123');
|
|
29
|
+
});
|
|
6
30
|
});
|
|
@@ -22,4 +22,82 @@ describe('deepMerge', () => {
|
|
|
22
22
|
expect(deepMerge(null, { a: 1 })).toEqual({ a: 1 });
|
|
23
23
|
expect(deepMerge({ a: 1 }, null)).toEqual({ a: 1 });
|
|
24
24
|
});
|
|
25
|
+
|
|
26
|
+
test('should handle arrays correctly', () => {
|
|
27
|
+
const target = { arr: [1, 2] };
|
|
28
|
+
const source = { arr: [3, 4] };
|
|
29
|
+
|
|
30
|
+
expect(deepMerge(target, source)).toEqual({ arr: [3, 4] });
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('should handle nested arrays correctly', () => {
|
|
34
|
+
const target = { obj: { arr: [1, 2] } };
|
|
35
|
+
const source = { obj: { arr: [3, 4] } };
|
|
36
|
+
|
|
37
|
+
expect(deepMerge(target, source)).toEqual({ obj: { arr: [3, 4] } });
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('should handle string values', () => {
|
|
41
|
+
const target = { str: 'hello' };
|
|
42
|
+
const source = { str: 'world' };
|
|
43
|
+
|
|
44
|
+
expect(deepMerge(target, source)).toEqual({ str: 'world' });
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('should handle number values', () => {
|
|
48
|
+
const target = { num: 42 };
|
|
49
|
+
const source = { num: 100 };
|
|
50
|
+
|
|
51
|
+
expect(deepMerge(target, source)).toEqual({ num: 100 });
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('should handle boolean values', () => {
|
|
55
|
+
const target = { bool: true };
|
|
56
|
+
const source = { bool: false };
|
|
57
|
+
|
|
58
|
+
expect(deepMerge(target, source)).toEqual({ bool: false });
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('should handle undefined values', () => {
|
|
62
|
+
const target = { undef: undefined };
|
|
63
|
+
const source = { undef: 'value' };
|
|
64
|
+
|
|
65
|
+
expect(deepMerge(target, source)).toEqual({ undef: 'value' });
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('should handle null values', () => {
|
|
69
|
+
const target = { nullVal: null };
|
|
70
|
+
const source = { nullVal: 'value' };
|
|
71
|
+
|
|
72
|
+
expect(deepMerge(target, source)).toEqual({ nullVal: 'value' });
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('should handle mixed property types', () => {
|
|
76
|
+
const target = {
|
|
77
|
+
str: 'hello',
|
|
78
|
+
num: 42,
|
|
79
|
+
arr: [1, 2],
|
|
80
|
+
obj: { nested: 'value' },
|
|
81
|
+
};
|
|
82
|
+
const source = {
|
|
83
|
+
str: 'world',
|
|
84
|
+
num: 100,
|
|
85
|
+
arr: [3, 4],
|
|
86
|
+
obj: { nested: 'newValue' },
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
expect(deepMerge(target, source)).toEqual({
|
|
90
|
+
str: 'world',
|
|
91
|
+
num: 100,
|
|
92
|
+
arr: [3, 4],
|
|
93
|
+
obj: { nested: 'newValue' },
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('should handle constructor and __proto__ protection', () => {
|
|
98
|
+
const target = { a: 1 };
|
|
99
|
+
const source = { b: 2 };
|
|
100
|
+
|
|
101
|
+
expect(deepMerge(target, source)).toEqual({ a: 1, b: 2 });
|
|
102
|
+
});
|
|
25
103
|
});
|
package/src/util/deg2rad.test.js
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { delayPromise } from './delayPromise.js';
|
|
2
|
+
|
|
3
|
+
describe('delayPromise', () => {
|
|
4
|
+
test('Returns a promise that resolves after specified delay', async () => {
|
|
5
|
+
const start = Date.now();
|
|
6
|
+
await delayPromise(10);
|
|
7
|
+
const end = Date.now();
|
|
8
|
+
|
|
9
|
+
// Should resolve within a reasonable time frame
|
|
10
|
+
expect(end - start).toBeGreaterThanOrEqual(10);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
test('Handles zero delay correctly', async () => {
|
|
14
|
+
const start = Date.now();
|
|
15
|
+
await delayPromise(0);
|
|
16
|
+
const end = Date.now();
|
|
17
|
+
|
|
18
|
+
// Should resolve immediately
|
|
19
|
+
expect(end - start).toBeGreaterThanOrEqual(0);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -4,5 +4,13 @@
|
|
|
4
4
|
* @returns {number} The fixed number.
|
|
5
5
|
*/
|
|
6
6
|
export function fixFloatPrecision(value) {
|
|
7
|
+
// Handle string inputs by converting to number first
|
|
8
|
+
if (typeof value === 'string') {
|
|
9
|
+
value = Number(value);
|
|
10
|
+
}
|
|
11
|
+
if (value >= 0 && value < 0.00000000001) {
|
|
12
|
+
const valuePlusOne = value + 1;
|
|
13
|
+
return Number.parseFloat(valuePlusOne.toPrecision(12)) - 1;
|
|
14
|
+
}
|
|
7
15
|
return Number.parseFloat(value.toPrecision(12));
|
|
8
16
|
}
|
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
import { fixFloatPrecision } from './fixFloatPrecision.js';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
describe('fixFloatPrecision', () => {
|
|
4
|
+
test('Fixes float precision issues', () => {
|
|
5
|
+
expect(fixFloatPrecision(0.20000000000000004)).toBe(0.2);
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
test('Handles zero', () => {
|
|
9
|
+
expect(fixFloatPrecision(0)).toBe(0);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test('Handles negative numbers', () => {
|
|
13
|
+
expect(fixFloatPrecision(-0.20000000000000004)).toBe(-0.2);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('Handles very small numbers', () => {
|
|
17
|
+
expect(fixFloatPrecision(0.0000000000001)).toBe(0);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('Handles integer numbers', () => {
|
|
21
|
+
expect(fixFloatPrecision(5)).toBe(5);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('Handles string input', () => {
|
|
25
|
+
expect(fixFloatPrecision('5.123456789')).toBe(5.123456789);
|
|
26
|
+
});
|
|
5
27
|
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Get object value by path.
|
|
3
|
-
* @param {object} obj -
|
|
4
|
-
* @param {string} path -
|
|
5
|
-
* @returns {object | null}
|
|
3
|
+
* @param {object} obj - The source object to get the value from.
|
|
4
|
+
* @param {string} path - The path to the property in dot notation (e.g. 'a.b.c').
|
|
5
|
+
* @returns {object | null} The value at the specified path or null if not found.
|
|
6
6
|
*/
|
|
7
7
|
export const getObjValueByPath = (obj, path) => {
|
|
8
8
|
if (!obj || !path) {
|
|
@@ -11,7 +11,10 @@ export const getObjValueByPath = (obj, path) => {
|
|
|
11
11
|
const keyParts = path.split('.');
|
|
12
12
|
const nextKey = keyParts[0];
|
|
13
13
|
if (keyParts.length === 1) {
|
|
14
|
-
return obj[nextKey];
|
|
14
|
+
return obj[nextKey] === undefined ? null : obj[nextKey];
|
|
15
|
+
}
|
|
16
|
+
if (obj[nextKey] === undefined || obj[nextKey] === null) {
|
|
17
|
+
return null;
|
|
15
18
|
}
|
|
16
19
|
return getObjValueByPath(obj[nextKey], keyParts.slice(1).join('.'));
|
|
17
20
|
};
|
|
@@ -1,6 +1,51 @@
|
|
|
1
1
|
import { getObjValueByPath } from './getObjValueByPath.js';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
describe('getObjValueByPath', () => {
|
|
4
|
+
test('Gets object value by path', () => {
|
|
5
|
+
const source = { a: { b: { c: 'd' } } };
|
|
6
|
+
expect(getObjValueByPath(source, 'a.b.c')).toBe('d');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
test('Returns null when object is null or undefined', () => {
|
|
10
|
+
expect(getObjValueByPath(null, 'a.b.c')).toBeNull();
|
|
11
|
+
expect(getObjValueByPath(undefined, 'a.b.c')).toBeNull();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test('Returns null when path is empty or null', () => {
|
|
15
|
+
expect(getObjValueByPath({ a: 'b' }, '')).toBeNull();
|
|
16
|
+
expect(getObjValueByPath({ a: 'b' }, null)).toBeNull();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('Returns null when property does not exist', () => {
|
|
20
|
+
const source = { a: { b: 'c' } };
|
|
21
|
+
expect(getObjValueByPath(source, 'a.b.c')).toBeNull();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('Returns null when property is undefined', () => {
|
|
25
|
+
const source = { a: { b: undefined } };
|
|
26
|
+
expect(getObjValueByPath(source, 'a.b')).toBeNull();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('Handles single-level paths correctly', () => {
|
|
30
|
+
const source = { a: 'value' };
|
|
31
|
+
expect(getObjValueByPath(source, 'a')).toBe('value');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('Handles nested paths correctly', () => {
|
|
35
|
+
const source = {
|
|
36
|
+
level1: {
|
|
37
|
+
level2: {
|
|
38
|
+
level3: 'deepValue',
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
expect(getObjValueByPath(source, 'level1.level2.level3')).toBe('deepValue');
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('Handles arrays in paths', () => {
|
|
46
|
+
const source = {
|
|
47
|
+
items: [{ name: 'item1' }, { name: 'item2' }],
|
|
48
|
+
};
|
|
49
|
+
expect(getObjValueByPath(source, 'items.0.name')).toBe('item1');
|
|
50
|
+
});
|
|
6
51
|
});
|
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
import { getRandomInt } from './getRandomInt.js';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
describe('getRandomInt', () => {
|
|
4
|
+
test('Returns random integer within range when min equals max', () => {
|
|
5
|
+
expect(getRandomInt(1, 1)).toBe(1);
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
test('Returns random integer within range when min is less than max', () => {
|
|
9
|
+
const result = getRandomInt(1, 10);
|
|
10
|
+
expect(result).toBeGreaterThanOrEqual(1);
|
|
11
|
+
expect(result).toBeLessThanOrEqual(10);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test('Works with negative numbers', () => {
|
|
15
|
+
const result = getRandomInt(-5, -1);
|
|
16
|
+
expect(result).toBeGreaterThanOrEqual(-5);
|
|
17
|
+
expect(result).toBeLessThanOrEqual(-1);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('Works with zero range', () => {
|
|
21
|
+
const result = getRandomInt(0, 0);
|
|
22
|
+
expect(result).toBe(0);
|
|
23
|
+
});
|
|
5
24
|
});
|
package/src/util/getURLParam.js
CHANGED
|
@@ -3,12 +3,12 @@ import { sanitizeURLParam } from './sanitizeURLParam.js';
|
|
|
3
3
|
const urlSearchParams = new URLSearchParams(window.location.search);
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Get a URL parameter value.
|
|
7
7
|
* @template T
|
|
8
|
-
* @param {string} key -
|
|
9
|
-
* @param {T} defaultValue -
|
|
10
|
-
* @param {boolean} isSanitize -
|
|
11
|
-
* @returns {string | T}
|
|
8
|
+
* @param {string} key - The name of the URL parameter to retrieve.
|
|
9
|
+
* @param {T} defaultValue - The default value to return if the parameter is not found.
|
|
10
|
+
* @param {boolean} isSanitize - Whether to sanitize the parameter value.
|
|
11
|
+
* @returns {string | T} The URL parameter value or the default value if not found.
|
|
12
12
|
*/
|
|
13
13
|
export const getURLParam = (key, defaultValue = null, isSanitize = true) => {
|
|
14
14
|
const paramValue = urlSearchParams.get(key);
|
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
import { getURLParam } from './getURLParam.js';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
describe('getURLParam', () => {
|
|
4
|
+
test('Returns fallback value when parameter is not found', () => {
|
|
5
|
+
const result = getURLParam('nonexistent', 'fallback');
|
|
6
|
+
expect(result).toBe('fallback');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
test('Handles null/undefined input gracefully', () => {
|
|
10
|
+
const result = getURLParam(null, 'fallback');
|
|
11
|
+
expect(result).toBe('fallback');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test('Returns default value when param is null', () => {
|
|
15
|
+
const result = getURLParam('key', 'default');
|
|
16
|
+
expect(result).toBe('default');
|
|
17
|
+
});
|
|
5
18
|
});
|
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
import { sanitizeURLParam } from './sanitizeURLParam.js';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
describe('sanitizeURLParam', () => {
|
|
4
|
+
test('Sanitizes URL parameter correctly', () => {
|
|
5
|
+
expect(sanitizeURLParam('abc<>-123[]{}-()A_BC')).toBe('abc-123-A_BC');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
test('Handles null input', () => {
|
|
9
|
+
expect(sanitizeURLParam(null)).toBe(null);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test('Handles empty string', () => {
|
|
13
|
+
expect(sanitizeURLParam('')).toBe('');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('Handles valid characters', () => {
|
|
17
|
+
expect(sanitizeURLParam('abc123')).toBe('abc123');
|
|
18
|
+
expect(sanitizeURLParam('test-parameter')).toBe('test-parameter');
|
|
19
|
+
expect(sanitizeURLParam('test_parameter')).toBe('test_parameter');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('Handles special characters', () => {
|
|
23
|
+
expect(sanitizeURLParam('test@#$%')).toBe('test');
|
|
24
|
+
expect(sanitizeURLParam('test!@#$%^&*()')).toBe('test');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('Handles unicode characters', () => {
|
|
28
|
+
expect(sanitizeURLParam('test_รครถรผ')).toBe('test_');
|
|
29
|
+
});
|
|
6
30
|
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* @param {object} obj -
|
|
4
|
-
* @param {string} path -
|
|
5
|
-
* @param {object | null | undefined} value -
|
|
2
|
+
* Set object value by path.
|
|
3
|
+
* @param {object} obj - The source object to set the value in.
|
|
4
|
+
* @param {string} path - The path to the property in dot notation (e.g. 'a.b.c').
|
|
5
|
+
* @param {object | null | undefined} value - The value to set at the specified path.
|
|
6
6
|
* @throws {SyntaxError} Error when illegal path value has been provided.
|
|
7
7
|
*/
|
|
8
8
|
export const setObjValueByPath = (obj, path, value) => {
|
|
@@ -16,6 +16,11 @@ export const setObjValueByPath = (obj, path, value) => {
|
|
|
16
16
|
}
|
|
17
17
|
if (keyParts.length === 1) {
|
|
18
18
|
obj[nextKey] = value;
|
|
19
|
+
} else {
|
|
20
|
+
// Create the nested object if it doesn't exist
|
|
21
|
+
if (obj[nextKey] === undefined || obj[nextKey] === null) {
|
|
22
|
+
obj[nextKey] = {};
|
|
23
|
+
}
|
|
24
|
+
setObjValueByPath(obj[nextKey], keyParts.slice(1).join('.'), value);
|
|
19
25
|
}
|
|
20
|
-
setObjValueByPath(obj[nextKey], keyParts.slice(1).join('.'), value);
|
|
21
26
|
};
|
|
@@ -1,9 +1,47 @@
|
|
|
1
1
|
import { getObjValueByPath } from './getObjValueByPath.js';
|
|
2
2
|
import { setObjValueByPath } from './setObjValueByPath.js';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
describe('setObjValueByPath', () => {
|
|
5
|
+
test('Sets object value by path', () => {
|
|
6
|
+
const source = { a: { b: { c: 'd' } } };
|
|
7
|
+
expect(getObjValueByPath(source, 'a.b.c')).toBe('d');
|
|
8
|
+
setObjValueByPath(source, 'a.b.c', 'newValue');
|
|
9
|
+
expect(getObjValueByPath(source, 'a.b.c')).toBe('newValue');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test('Handles null or undefined object', () => {
|
|
13
|
+
// Should not throw error
|
|
14
|
+
setObjValueByPath(null, 'a.b.c', 'value');
|
|
15
|
+
setObjValueByPath(undefined, 'a.b.c', 'value');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('Handles null or undefined path', () => {
|
|
19
|
+
const source = { a: 'b' };
|
|
20
|
+
// Should not throw error
|
|
21
|
+
setObjValueByPath(source, null, 'value');
|
|
22
|
+
setObjValueByPath(source, undefined, 'value');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('Sets value at root level', () => {
|
|
26
|
+
const source = { a: 'oldValue' };
|
|
27
|
+
setObjValueByPath(source, 'a', 'newValue');
|
|
28
|
+
expect(source.a).toBe('newValue');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('Creates new nested properties', () => {
|
|
32
|
+
const source = { a: { b: 'existing' } };
|
|
33
|
+
setObjValueByPath(source, 'a.c.d', 'newNestedValue');
|
|
34
|
+
expect(getObjValueByPath(source, 'a.c.d')).toBe('newNestedValue');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('Handles array paths', () => {
|
|
38
|
+
const source = { items: [{ name: 'item1' }] };
|
|
39
|
+
setObjValueByPath(source, 'items.0.name', 'updatedItem');
|
|
40
|
+
expect(getObjValueByPath(source, 'items.0.name')).toBe('updatedItem');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('Throws error for __proto__ path', () => {
|
|
44
|
+
const source = { a: 'b' };
|
|
45
|
+
expect(() => setObjValueByPath(source, '__proto__.test', 'value')).toThrow(SyntaxError);
|
|
46
|
+
});
|
|
9
47
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { underscoreToCamelCase } from './underscoreToCamelCase.js';
|
|
2
2
|
|
|
3
|
-
test('
|
|
3
|
+
test('Converts underscore to camelCase', () => {
|
|
4
4
|
expect(underscoreToCamelCase('test')).toBe('test');
|
|
5
5
|
expect(underscoreToCamelCase('test_variable')).toBe('testVariable');
|
|
6
6
|
expect(underscoreToCamelCase('test_variable_name')).toBe('testVariableName');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fixFloatPrecision.d.ts","sourceRoot":"","sources":["../../src/util/fixFloatPrecision.js"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,yCAHW,MAAM,GACJ,MAAM,
|
|
1
|
+
{"version":3,"file":"fixFloatPrecision.d.ts","sourceRoot":"","sources":["../../src/util/fixFloatPrecision.js"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,yCAHW,MAAM,GACJ,MAAM,CAYlB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getObjValueByPath.d.ts","sourceRoot":"","sources":["../../src/util/getObjValueByPath.js"],"names":[],"mappings":"AAMO,uCAJI,MAAM,QACN,MAAM,GACJ,MAAM,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"getObjValueByPath.d.ts","sourceRoot":"","sources":["../../src/util/getObjValueByPath.js"],"names":[],"mappings":"AAMO,uCAJI,MAAM,QACN,MAAM,GACJ,MAAM,GAAG,IAAI,CAezB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setObjValueByPath.d.ts","sourceRoot":"","sources":["../../src/util/setObjValueByPath.js"],"names":[],"mappings":"AAOO,uCALI,MAAM,QACN,MAAM,SACN,MAAM,GAAG,IAAI,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"setObjValueByPath.d.ts","sourceRoot":"","sources":["../../src/util/setObjValueByPath.js"],"names":[],"mappings":"AAOO,uCALI,MAAM,QACN,MAAM,SACN,MAAM,GAAG,IAAI,GAAG,SAAS,QAqBnC"}
|
package/.oxlintrc.json
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
|
3
|
-
"env": { "browser": true },
|
|
4
|
-
"plugins": [
|
|
5
|
-
"eslint",
|
|
6
|
-
"import",
|
|
7
|
-
"jsdoc",
|
|
8
|
-
"jsx-a11y",
|
|
9
|
-
"oxc",
|
|
10
|
-
"promise",
|
|
11
|
-
"react-perf",
|
|
12
|
-
"react",
|
|
13
|
-
"typescript",
|
|
14
|
-
"unicorn"
|
|
15
|
-
],
|
|
16
|
-
"categories": {
|
|
17
|
-
"correctness": "warn",
|
|
18
|
-
"nursery": "warn",
|
|
19
|
-
"pedantic": "warn",
|
|
20
|
-
"perf": "warn",
|
|
21
|
-
"restriction": "warn",
|
|
22
|
-
"style": "warn",
|
|
23
|
-
"suspicious": "warn"
|
|
24
|
-
},
|
|
25
|
-
"rules": {
|
|
26
|
-
"arrow-body-style": "off",
|
|
27
|
-
"class-methods-use-this": "off",
|
|
28
|
-
"consistent-type-imports": "off",
|
|
29
|
-
"curly": "off",
|
|
30
|
-
"default-param-last": "off",
|
|
31
|
-
"explicit-module-boundary-types": "off",
|
|
32
|
-
"exports-last": "off",
|
|
33
|
-
"extensions": "off",
|
|
34
|
-
"filename-case": "off",
|
|
35
|
-
"first": "off",
|
|
36
|
-
"func-style": "off",
|
|
37
|
-
"group-exports": "off",
|
|
38
|
-
"id-length": "off",
|
|
39
|
-
"init-declarations": "off",
|
|
40
|
-
"max-dependencies": "off",
|
|
41
|
-
"max-params": "off",
|
|
42
|
-
"no-anonymous-default-export": "off",
|
|
43
|
-
"no-async-await": "off",
|
|
44
|
-
"no-await-in-loop": "off",
|
|
45
|
-
"no-console": "off",
|
|
46
|
-
"no-continue": "off",
|
|
47
|
-
"no-default-export": "off",
|
|
48
|
-
"no-empty-interface": "off",
|
|
49
|
-
"no-empty-object-type": "off",
|
|
50
|
-
"no-explicit-any": "off",
|
|
51
|
-
"no-magic-numbers": "off",
|
|
52
|
-
"no-named-export": "off",
|
|
53
|
-
"no-null": "off",
|
|
54
|
-
"no-optional-chaining": "off",
|
|
55
|
-
"no-param-reassign": "off",
|
|
56
|
-
"no-rest-spread-properties": "off",
|
|
57
|
-
"no-ternary": "off",
|
|
58
|
-
"no-undef": "off",
|
|
59
|
-
"no-undefined": "off",
|
|
60
|
-
"no-unused-expressions": "off",
|
|
61
|
-
"no-unused-vars": "off",
|
|
62
|
-
"numeric-operators-style": "off",
|
|
63
|
-
"numeric-separators-style": "off",
|
|
64
|
-
"prefer-default-export": "off",
|
|
65
|
-
"prefer-destructuring": "off",
|
|
66
|
-
"prefer-event-target": "off",
|
|
67
|
-
"prefer-global-this": "off",
|
|
68
|
-
"require-module-specifiers": "off",
|
|
69
|
-
"sort-imports": "off",
|
|
70
|
-
"sort-keys": "off"
|
|
71
|
-
}
|
|
72
|
-
}
|
package/commitlint.config.js
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
const config = {
|
|
2
|
-
rules: {
|
|
3
|
-
'body-leading-blank': [1, 'always'],
|
|
4
|
-
'body-max-line-length': [2, 'always', 100],
|
|
5
|
-
'footer-leading-blank': [1, 'always'],
|
|
6
|
-
'footer-max-line-length': [2, 'always', 100],
|
|
7
|
-
'header-max-length': [2, 'always', 100],
|
|
8
|
-
'header-trim': [2, 'always'],
|
|
9
|
-
'subject-case': [2, 'never', ['sentence-case', 'start-case', 'pascal-case', 'upper-case']],
|
|
10
|
-
'subject-empty': [2, 'never'],
|
|
11
|
-
'subject-full-stop': [2, 'never', '.'],
|
|
12
|
-
'type-case': [2, 'always', 'lower-case'],
|
|
13
|
-
'type-empty': [2, 'never'],
|
|
14
|
-
'type-enum': [
|
|
15
|
-
2,
|
|
16
|
-
'always',
|
|
17
|
-
['build', 'chore', 'ci', 'docs', 'feat', 'fix', 'perf', 'refactor', 'revert', 'style', 'test'],
|
|
18
|
-
],
|
|
19
|
-
},
|
|
20
|
-
prompt: {
|
|
21
|
-
questions: {
|
|
22
|
-
type: {
|
|
23
|
-
description: "Select the type of change that you're committing",
|
|
24
|
-
enum: {
|
|
25
|
-
feat: {
|
|
26
|
-
description: 'A new feature',
|
|
27
|
-
title: 'Features',
|
|
28
|
-
emoji: 'โจ',
|
|
29
|
-
},
|
|
30
|
-
fix: {
|
|
31
|
-
description: 'A bug fix',
|
|
32
|
-
title: 'Bug Fixes',
|
|
33
|
-
emoji: '๐',
|
|
34
|
-
},
|
|
35
|
-
docs: {
|
|
36
|
-
description: 'Documentation only changes',
|
|
37
|
-
title: 'Documentation',
|
|
38
|
-
emoji: '๐',
|
|
39
|
-
},
|
|
40
|
-
style: {
|
|
41
|
-
description:
|
|
42
|
-
'Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)',
|
|
43
|
-
title: 'Styles',
|
|
44
|
-
emoji: '๐',
|
|
45
|
-
},
|
|
46
|
-
refactor: {
|
|
47
|
-
description: 'A code change that neither fixes a bug nor adds a feature',
|
|
48
|
-
title: 'Code Refactoring',
|
|
49
|
-
emoji: '๐ฆ',
|
|
50
|
-
},
|
|
51
|
-
perf: {
|
|
52
|
-
description: 'A code change that improves performance',
|
|
53
|
-
title: 'Performance Improvements',
|
|
54
|
-
emoji: '๐',
|
|
55
|
-
},
|
|
56
|
-
test: {
|
|
57
|
-
description: 'Adding missing tests or correcting existing tests',
|
|
58
|
-
title: 'Tests',
|
|
59
|
-
emoji: '๐จ',
|
|
60
|
-
},
|
|
61
|
-
build: {
|
|
62
|
-
description:
|
|
63
|
-
'Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)',
|
|
64
|
-
title: 'Builds',
|
|
65
|
-
emoji: '๐ ',
|
|
66
|
-
},
|
|
67
|
-
ci: {
|
|
68
|
-
description:
|
|
69
|
-
'Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)',
|
|
70
|
-
title: 'Continuous Integrations',
|
|
71
|
-
emoji: 'โ๏ธ',
|
|
72
|
-
},
|
|
73
|
-
chore: {
|
|
74
|
-
description: "Other changes that don't modify src or test files",
|
|
75
|
-
title: 'Chores',
|
|
76
|
-
emoji: 'โป๏ธ',
|
|
77
|
-
},
|
|
78
|
-
revert: {
|
|
79
|
-
description: 'Reverts a previous commit',
|
|
80
|
-
title: 'Reverts',
|
|
81
|
-
emoji: '๐',
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
},
|
|
85
|
-
scope: {
|
|
86
|
-
description: 'What is the scope of this change (e.g. component or file name)',
|
|
87
|
-
},
|
|
88
|
-
subject: {
|
|
89
|
-
description: 'Write a short, imperative tense description of the change',
|
|
90
|
-
},
|
|
91
|
-
body: {
|
|
92
|
-
description: 'Provide a longer description of the change',
|
|
93
|
-
},
|
|
94
|
-
isBreaking: {
|
|
95
|
-
description: 'Are there any breaking changes?',
|
|
96
|
-
},
|
|
97
|
-
breakingBody: {
|
|
98
|
-
description: 'A BREAKING CHANGE commit requires a body. Please enter a longer description of the commit itself',
|
|
99
|
-
},
|
|
100
|
-
breaking: {
|
|
101
|
-
description: 'Describe the breaking changes',
|
|
102
|
-
},
|
|
103
|
-
isIssueAffected: {
|
|
104
|
-
description: 'Does this change affect any open issues?',
|
|
105
|
-
},
|
|
106
|
-
issuesBody: {
|
|
107
|
-
description:
|
|
108
|
-
'If issues are closed, the commit requires a body. Please enter a longer description of the commit itself',
|
|
109
|
-
},
|
|
110
|
-
issues: {
|
|
111
|
-
description: 'Add issue references (e.g. "fix #123", "re #123".)',
|
|
112
|
-
},
|
|
113
|
-
},
|
|
114
|
-
},
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
export default config;
|
package/eslint.config.js
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { defineConfig } from 'eslint/config';
|
|
2
|
-
import js from '@eslint/js';
|
|
3
|
-
import jsdocPlugin from 'eslint-plugin-jsdoc';
|
|
4
|
-
import unicornPlugin from 'eslint-plugin-unicorn';
|
|
5
|
-
import globals from 'globals';
|
|
6
|
-
|
|
7
|
-
export default defineConfig([
|
|
8
|
-
{
|
|
9
|
-
ignores: [
|
|
10
|
-
'.github/**/*.*',
|
|
11
|
-
'.idea/**/*.*',
|
|
12
|
-
'.vscode/**/*.*',
|
|
13
|
-
'build/**/*.*',
|
|
14
|
-
'coverage/**/*.*',
|
|
15
|
-
'dist/**/*.*',
|
|
16
|
-
'public/**/*.*',
|
|
17
|
-
'types/**/*.*',
|
|
18
|
-
'node_modules/**/*.*',
|
|
19
|
-
],
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
languageOptions: {
|
|
23
|
-
globals: {
|
|
24
|
-
...globals.vitest,
|
|
25
|
-
...globals.browser,
|
|
26
|
-
...globals.node,
|
|
27
|
-
...globals.es2026,
|
|
28
|
-
},
|
|
29
|
-
parserOptions: {
|
|
30
|
-
ecmaVersion: 'latest',
|
|
31
|
-
sourceType: 'module',
|
|
32
|
-
ecmaFeatures: {
|
|
33
|
-
jsx: true,
|
|
34
|
-
},
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
files: ['**/*.{js,jsx,mjs,cjs,ts,tsx}'],
|
|
38
|
-
plugins: {
|
|
39
|
-
jsdoc: jsdocPlugin,
|
|
40
|
-
unicorn: unicornPlugin,
|
|
41
|
-
},
|
|
42
|
-
settings: {
|
|
43
|
-
'import/parsers': {
|
|
44
|
-
espree: ['.js', '.cjs', '.mjs', '.jsx'],
|
|
45
|
-
},
|
|
46
|
-
'import/resolver': {
|
|
47
|
-
node: true,
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
rules: {
|
|
51
|
-
...js.configs.recommended.rules,
|
|
52
|
-
...jsdocPlugin.configs['flat/recommended'].rules,
|
|
53
|
-
...unicornPlugin.configs.recommended.rules,
|
|
54
|
-
'no-unused-vars': 'off',
|
|
55
|
-
'unicorn/filename-case': 'off',
|
|
56
|
-
'unicorn/no-null': 'off',
|
|
57
|
-
'unicorn/numeric-separators-style': 'off',
|
|
58
|
-
'unicorn/prefer-global-this': 'off',
|
|
59
|
-
'unicorn/prevent-abbreviations': 'off',
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
|
-
]);
|
package/lefthook.yml
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# Lefthook configuration
|
|
2
|
-
# @namespace javascript
|
|
3
|
-
# @copyright Copyright (c) 2025-present VPMedia
|
|
4
|
-
# @author Andras Csizmadia <andras@vpmedia.hu>
|
|
5
|
-
# @version 1.0.0
|
|
6
|
-
# @see https://github.com/evilmartians/lefthook/blob/master/docs/configuration.md
|
|
7
|
-
|
|
8
|
-
pre-commit:
|
|
9
|
-
parallel: false
|
|
10
|
-
commands:
|
|
11
|
-
lint_yaml:
|
|
12
|
-
glob: "*.{yml,yaml}"
|
|
13
|
-
run: yq 'true' {staged_files} > /dev/null
|
|
14
|
-
format_js:
|
|
15
|
-
glob: "*.{js,jsx}"
|
|
16
|
-
run: |
|
|
17
|
-
set -e
|
|
18
|
-
pnpm exec prettier --write -- {staged_files} || true
|
|
19
|
-
git add {staged_files} &> /dev/null || true
|
|
20
|
-
lint_js:
|
|
21
|
-
glob: "*.{js,jsx}"
|
|
22
|
-
run: pnpm exec eslint {staged_files}
|
|
23
|
-
commit-msg:
|
|
24
|
-
commands:
|
|
25
|
-
commitlint:
|
|
26
|
-
run: pnpm exec commitlint --edit {1}
|
package/pnpm-workspace.yaml
DELETED