@semantic-ui/utils
Advanced tools
Comparing version 0.1.2 to 0.1.3
@@ -11,3 +11,3 @@ { | ||
}, | ||
"version": "0.1.2" | ||
"version": "0.1.3" | ||
} |
136
src/utils.js
@@ -89,2 +89,7 @@ /* | ||
export const getJSON = async (src) => { | ||
const response = await fetch(src); | ||
return await response.json(); | ||
}; | ||
/*------------------- | ||
@@ -320,7 +325,7 @@ Types | ||
*/ | ||
export const memoize = (fn) => { | ||
export const memoize = (fn, hashFunction = (args) => hashCode(JSON.stringify(args))) => { | ||
const cache = new Map(); | ||
return function(...args) { | ||
const key = hashCode(JSON.stringify(args)); | ||
const key = hashFunction(args); | ||
@@ -861,2 +866,129 @@ if (cache.has(key)) { | ||
/* | ||
Searches a search object | ||
returning matches for a query | ||
Matches are sorted | ||
- Start of word | ||
- Start of any word | ||
- Anywhere in string | ||
*/ | ||
export const weightedObjectSearch = (query = '', objectArray = [], { | ||
returnMatches = false, | ||
matchAllWords = true, | ||
propertiesToMatch = [] | ||
} = {}) => { | ||
if(!query) { | ||
return objectArray; | ||
} | ||
query = query.trim(); | ||
query = escapeRegExp(query); | ||
let | ||
words = query.split(' '), | ||
wordRegexes = [], | ||
regexes = { | ||
startsWith : new RegExp(`^${query}`, 'i'), | ||
wordStartsWith : new RegExp(`\\s${query}`, 'i'), | ||
anywhere : new RegExp(query, 'i') | ||
}, | ||
weights = { | ||
startsWith : 1, | ||
wordStartsWith : 2, | ||
anywhere : 3, | ||
anyWord : 4, | ||
}, | ||
calculateWeight = (obj) => { | ||
let | ||
matchDetails = [], | ||
weight | ||
; | ||
// do a weighted search across all fields | ||
each(propertiesToMatch, (field) => { | ||
let | ||
value = get(obj, field), | ||
fieldWeight | ||
; | ||
if(value) { | ||
each(regexes, (regex, name) => { | ||
if(fieldWeight) { | ||
return; | ||
} | ||
if(String(value).search(regex) !== -1) { | ||
fieldWeight = weights[name]; | ||
if(returnMatches) { | ||
matchDetails.push({ | ||
field, | ||
query, | ||
name, | ||
value, | ||
weight: fieldWeight, | ||
}); | ||
} | ||
} | ||
}); | ||
// match any word higher score for more words | ||
if(!weight && wordRegexes.length) { | ||
let wordsMatching = 0; | ||
each(wordRegexes, regex => { | ||
if(String(value).search(regex) !== -1) { | ||
wordsMatching++; | ||
} | ||
}); | ||
if(wordsMatching > 0) { | ||
if(!matchAllWords || (matchAllWords && wordsMatching === wordRegexes.length)) { | ||
fieldWeight = weights['anyWord'] / wordsMatching; | ||
if(returnMatches) { | ||
matchDetails.push({ | ||
field, | ||
query, | ||
name: 'anyWord', | ||
value, | ||
matchCount: wordsMatching | ||
}); | ||
} | ||
} | ||
} | ||
} | ||
if(fieldWeight && (!weight || fieldWeight < weight)) { | ||
weight = fieldWeight; | ||
} | ||
} | ||
}); | ||
// flag for removal if not a match | ||
if(returnMatches) { | ||
obj.matches = matchDetails; | ||
} | ||
obj.remove = !weight; | ||
return weight; | ||
} | ||
; | ||
if(objectArray.length == 1) { | ||
objectArray.push([]); | ||
} | ||
if(words.length > 1) { | ||
each(words, word => { | ||
wordRegexes.push(new RegExp(`(\W|^)${word}(\W|$)`, 'i')); | ||
}); | ||
} | ||
each(objectArray, obj => { | ||
// clear previous remove flag and weight if present | ||
delete obj.remove; | ||
delete obj.weight; | ||
obj.weight = calculateWeight(obj); | ||
}); | ||
let result = objectArray | ||
.filter(obj => !obj.remove) | ||
.sort((a, b) => { | ||
return a.weight - b.weight; | ||
}) | ||
; | ||
return result; | ||
}; | ||
/*------------------- | ||
@@ -863,0 +995,0 @@ Array / Object |
@@ -64,2 +64,3 @@ import { describe, beforeEach, afterEach, expect, it, vi } from 'vitest'; | ||
values, | ||
weightedObjectSearch, | ||
where, | ||
@@ -803,2 +804,44 @@ wrapFunction | ||
}); | ||
it('should create a new object without mutating the original', () => { | ||
const original = { a: 1, b: 2, c: 3 }; | ||
const mapped = mapObject(original, x => x * 2); | ||
// Check mapped values are correct | ||
expect(mapped).toEqual({ a: 2, b: 4, c: 6 }); | ||
// Verify original wasn't mutated | ||
expect(original).toEqual({ a: 1, b: 2, c: 3 }); | ||
// Verify it's a different object reference | ||
expect(mapped).not.toBe(original); | ||
}); | ||
it('should pass both value and key to the callback', () => { | ||
const original = { a: 1, b: 2 }; | ||
const spy = vi.fn((value, key) => value); | ||
mapObject(original, spy); | ||
expect(spy).toHaveBeenCalledWith(1, 'a'); | ||
expect(spy).toHaveBeenCalledWith(2, 'b'); | ||
}); | ||
it('should handle empty objects', () => { | ||
const original = {}; | ||
const mapped = mapObject(original, x => x * 2); | ||
expect(mapped).toEqual({}); | ||
}); | ||
it('should handle non-numeric values', () => { | ||
const original = { a: 'hello', b: 'world' }; | ||
const mapped = mapObject(original, str => str.toUpperCase()); | ||
expect(mapped).toEqual({ a: 'HELLO', b: 'WORLD' }); | ||
expect(original).toEqual({ a: 'hello', b: 'world' }); | ||
}); | ||
it('should preserve keys while mapping values', () => { | ||
const original = { key1: 1, key2: 2, key3: 3 }; | ||
const mapped = mapObject(original, x => x.toString()); | ||
expect(mapped).toEqual({ key1: '1', key2: '2', key3: '3' }); | ||
}); | ||
}); | ||
@@ -1277,2 +1320,33 @@ | ||
}); | ||
it('should allow custom hashing function', () => { | ||
const originalFunction = vi.fn((obj) => obj.a + obj.b); | ||
const customHash = (args) => args[0].id; | ||
const memoizedFunction = memoize(originalFunction, customHash); | ||
const result1 = memoizedFunction({id: 1, a: 2, b: 3}); | ||
expect(result1).toBe(5); | ||
expect(originalFunction).toHaveBeenCalledTimes(1); | ||
const result2 = memoizedFunction({id: 1, a: 3, b: 4}); | ||
expect(result2).toBe(5); | ||
expect(originalFunction).toHaveBeenCalledTimes(1); // Should not be called again due to same id | ||
const result3 = memoizedFunction({id: 2, a: 2, b: 3}); | ||
expect(result3).toBe(5); | ||
expect(originalFunction).toHaveBeenCalledTimes(2); // Should be called again due to different id | ||
}); | ||
it('should handle edge cases with custom hashing', () => { | ||
const originalFunction = vi.fn((a, b) => a + b); | ||
const alwaysSameHash = () => 'same'; | ||
const memoizedFunction = memoize(originalFunction, alwaysSameHash); | ||
const result1 = memoizedFunction(2, 3); | ||
expect(result1).toBe(5); | ||
expect(originalFunction).toHaveBeenCalledTimes(1); | ||
const result2 = memoizedFunction(4, 5); | ||
expect(result2).toBe(5); // Note: This is the memoized result, not 9 | ||
expect(originalFunction).toHaveBeenCalledTimes(1); // Should not be called again due to same hash | ||
}); | ||
}); | ||
@@ -1279,0 +1353,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
101692
2877
3