Skip to content

Commit

Permalink
new utils
Browse files Browse the repository at this point in the history
  • Loading branch information
tborychowski committed Jun 18, 2024
1 parent 6af59f9 commit 076c872
Show file tree
Hide file tree
Showing 11 changed files with 347 additions and 114 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ Changelog
=========


## v9.5.6 *(2024-06-18)*
- Added 2 new functions to utils `getValueAtPath` and `setValueAtPath` to handle deeply and uncertain object structures.


## v9.5.5 *(2024-06-13)*
- Improve `InputNumber` and `InputMath` to better filter out invalid characters and keep the formatting.

Expand Down
20 changes: 20 additions & 0 deletions docs-src/components/utils/functions/get-value-at-path.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Util id="getValueAtPath" name="getValueAtPath(obj, path, defaultValue)" {example}>
<p>Get a value from a given path in an object.</p>
</Util>


<script>
import Util from '../Util.svelte';
const example = `
<script>
const obj = {
a: [
{ b: { c: 1 } }
]
};
getValueAtPath(obj, 'a[0].b.c', 123)
&lt;/script>
`;
</script>
8 changes: 5 additions & 3 deletions docs-src/components/utils/functions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ export { default as empty } from './empty.svelte';
export { default as isset } from './isset.svelte';
export { default as formatDate } from './format-date.svelte';
export { default as fuzzy } from './fuzzy.svelte';
export { default as getMouseX } from './get-mouse-x.svelte.svelte';
export { default as getMouseXY } from './get-mouse-xy.svelte.svelte';
export { default as getMouseY } from './get-mouse-y.svelte.svelte';
export { default as getMouseX } from './get-mouse-x.svelte';
export { default as getMouseXY } from './get-mouse-xy.svelte';
export { default as getMouseY } from './get-mouse-y.svelte';
export { default as getPathFromObject } from './get-value-at-path.svelte';
export { default as guid } from './guid.svelte';
export { default as isInScrollable } from './is-in-scrollable.svelte';
export { default as isMobile } from './is-mobile.svelte';
export { default as isColorDark } from './is-color-dark.svelte';
export { default as pluck } from './pluck.svelte';
export { default as roundAmount } from './round-amount.svelte';
export { default as setPathOnObject } from './set-value-at-path.svelte';
export { default as throttle } from './throttle.svelte';
export { default as timeAgo } from './time-ago.svelte';
20 changes: 20 additions & 0 deletions docs-src/components/utils/functions/set-value-at-path.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Util id="setValueAtPath" name="setValueAtPath(obj, path, value)" {example}>
<p>Set a value for a given path in an object.</p>
</Util>


<script>
import Util from '../Util.svelte';
const example = `
<script>
const obj = {
a: [
{ b: { c: 1 } }
]
};
setValueAtPath(obj, 'a[0].b.c', 123)
&lt;/script>
`;
</script>
4 changes: 4 additions & 0 deletions docs-src/pages/changelog.svelte
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
<h1>Changelog</h1>
<h2>v9.5.6 <em>(2024-06-18)</em></h2>
<ul>
<li>Added 2 new functions to utils <code>getValueAtPath</code> and <code>setValueAtPath</code> to handle deeply and uncertain object structures.</li>
</ul>
<h2>v9.5.5 <em>(2024-06-13)</em></h2>
<ul>
<li>Improve <code>InputNumber</code> and <code>InputMath</code> to better filter out invalid characters and keep the formatting.</li>
Expand Down
242 changes: 131 additions & 111 deletions docs/docs.js

Large diffs are not rendered by default.

44 changes: 44 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,50 @@ export function deepCopy (o) {
}


/**
* Get a value from an object for a given path
* @param obj {object}
* @param path {string} - e.g. child[4]['some name][2].property
* @param defaultValue {*}
* @returns {*}
*/
export function getValueAtPath (obj, path, defaultValue) {
try {
return path
.replace(/^\./, '') // strip a leading dot
.replace(/\[['"]?([\w\s]+)['"]?]/ig, '.$1') // convert indexes to properties
.split('.')
.reduce((acc, key) => acc && acc[key], obj) || defaultValue;
}
catch {
return defaultValue;
}
}


/**
* Set a value on an object for a given path.
* @param obj
* @param path
* @param value
* @returns {boolean}
*/
export function setValueAtPath (obj, path, value) {
const keys = path
.replace(/^\./, '') // strip a leading dot
.replace(/\[['"]?([\w\s]+)['"]?]/ig, '.$1') // convert indexes to properties
.split('.');
const lastKey = keys.pop();
const lastObj = keys.reduce((acc, key) => {
if (typeof acc[key] === 'object') return acc[key];
return acc[key] = {};
}, obj);
if (!lastObj) return false;

lastObj[lastKey] = value;
}


export function throttle (fn, delay = 300) {
let lastCalled = 0;
return (...args) => {
Expand Down
119 changes: 119 additions & 0 deletions tests/utils.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,123 @@ describe('utils - deepCopy', () => {
});


describe('getValueAtPath', () => {
const data = {
child: {
grandchild: 'value',
array: [
{ child: [{}, {}, { abc: 'nested value' }] }
],
'abc def': 'space key',
abc: [
{ name: 'first' },
{ name: 'second' },
{ name: 'third' },
{ name: 'fourth' }
]
}
};

test('should return the value at the specified path', () => {
expect(utils.getValueAtPath(data, 'child.grandchild', 'default')).toBe('value');
});

test('should return the default value if the path does not exist', () => {
expect(utils.getValueAtPath(data, 'child.nonexistent', 'default')).toBe('default');
});

test('should handle array indices in the path', () => {
expect(utils.getValueAtPath(data, 'child.array[0].child[2].abc', 'default')).toBe('nested value');
});

test('should handle paths with strings as keys', () => {
expect(utils.getValueAtPath(data, "child['abc'][3].name", 'default')).toBe('fourth');
expect(utils.getValueAtPath(data, "child['abc def']", 'default')).toBe('space key');
});

test('should handle paths with double quotes as keys', () => {
expect(utils.getValueAtPath(data, 'child["abc"][3].name', 'default')).toBe('fourth');
expect(utils.getValueAtPath(data, 'child["abc def"]', 'default')).toBe('space key');
});

test('should return the default value if the path is invalid', () => {
expect(utils.getValueAtPath(data, 'child.array[0].child[2].nonexistent', 'default')).toBe('default');
});

test('should return the default value if the object is null or undefined', () => {
expect(utils.getValueAtPath(null, 'child.grandchild', 'default')).toBe('default');
expect(utils.getValueAtPath(undefined, 'child.grandchild', 'default')).toBe('default');
});

test('should handle paths with leading dots', () => {
expect(utils.getValueAtPath(data, '.child.grandchild', 'default')).toBe('value');
});

test('should handle paths with spaces in keys', () => {
expect(utils.getValueAtPath(data, "child['abc def']", 'default')).toBe('space key');
});
});


describe('setValueAtPath', () => {
let data;

beforeEach(() => {
data = {
child: {
grandchild: 'value',
array: [
{ child: [{}, {}, { abc: 'nested value' }] }
],
'abc def': 'space key',
abc: [
{ name: 'first' },
{ name: 'second' },
{ name: 'third' },
{ name: 'fourth' }
]
}
};
});

test('should set the value at the specified path', () => {
utils.setValueAtPath(data, 'child.newKey', 'newValue');
expect(data.child.newKey).toBe('newValue');
});

test('should create nested objects if they do not exist', () => {
utils.setValueAtPath(data, 'child.new.nested.key', 'newValue');
expect(data.child.new.nested.key).toBe('newValue');
});

test('should handle array indices in the path', () => {
utils.setValueAtPath(data, 'child.array[0].child[2].newKey', 'newValue');
expect(data.child.array[0].child[2].newKey).toBe('newValue');
});

test('should handle paths with strings as keys', () => {
utils.setValueAtPath(data, "child['abc'][3].newKey", 'newValue');
expect(data.child.abc[3].newKey).toBe('newValue');
});

test('should handle paths with double quotes as keys', () => {
utils.setValueAtPath(data, 'child["abc"][3].newKey', 'newValue');
expect(data.child.abc[3].newKey).toBe('newValue');
});

test('should handle paths with leading dots', () => {
utils.setValueAtPath(data, '.child.newKey', 'newValue');
expect(data.child.newKey).toBe('newValue');
});

test('should handle paths with spaces in keys', () => {
utils.setValueAtPath(data, "child['abc def'].newKey", 'newValue');
expect(data.child['abc def'].newKey).toBe('newValue');
});

});


test('utils - throttle', async () => {
const fn = jest.fn();
const throttled = utils.throttle(fn, 100);
Expand Down Expand Up @@ -407,3 +524,5 @@ describe('isColorDark', () => {
expect(utils.isColorDark('#gggggg')).toBe(false);
});
});


0 comments on commit 076c872

Please sign in to comment.