@semantic-ui/utils
Advanced tools
Comparing version 0.0.3 to 0.0.5
{ | ||
"name": "@semantic-ui/utils", | ||
"version": "0.0.3", | ||
"version": "0.0.5", | ||
"type": "module", | ||
@@ -9,10 +9,2 @@ "main": "src/utils.js", | ||
"license": "ISC", | ||
"scripts": { | ||
"publish": "wireit" | ||
}, | ||
"wireit": { | ||
"publish": { | ||
"command": "npm publish" | ||
} | ||
}, | ||
"devDependencies": { | ||
@@ -19,0 +11,0 @@ "vitest": "^1.5.2" |
@@ -28,2 +28,3 @@ # @semantic-ui/utils | ||
- `some/any(arr, truthFunc)` - Returns true if any/some values match truthFunc | ||
- `sum(values)` - sums an array of numbers | ||
@@ -44,2 +45,3 @@ | ||
- `reverseKeys(obj)` - Reverses a lookup object's keys and values. | ||
- `arrayFromObject(obj) - Returns an array with key value pairs from an object | ||
@@ -59,2 +61,3 @@ ### Types | ||
- `isNode(x)` - Checks if the value is a DOM node. | ||
- `isEmpty(x)` Checks if the value is empty like {} | ||
@@ -77,2 +80,3 @@ ### Date | ||
- `escapeRegExp(string)` - Escapes special characters in a string for use in a regular expression. | ||
- `escapeHTML(string)` - Escapes string for html '&<>"' only | ||
@@ -79,0 +83,0 @@ ### Looping |
162
src/utils.js
@@ -119,2 +119,19 @@ /* | ||
}; | ||
export const isEmpty = (x) => { | ||
// we want nullish here | ||
if (x == null) { | ||
return true; | ||
} | ||
if (isArray(x) || isString(x)) { | ||
return x.length === 0; | ||
} | ||
for (let key in x) { | ||
if (x[key]) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
}; | ||
/*------------------- | ||
@@ -359,2 +376,6 @@ Date | ||
export const sum = (values) => { | ||
return values.reduce((acc, num) => acc + num, 0); | ||
}; | ||
export const where = (array, properties) => { | ||
@@ -380,2 +401,36 @@ return array.filter((obj) => | ||
export const sortBy = function (arr, key, comparator) { | ||
const compare = (a, b) => { | ||
const valA = get(a, key); | ||
const valB = get(b, key); | ||
if (valA === undefined && valB === undefined) return 0; | ||
if (valA === undefined) return 1; // Place undefined values at the end | ||
if (valB === undefined) return -1; // Place undefined values at the end | ||
if (comparator) { | ||
return comparator(valA, valB, a, b); | ||
} | ||
if (valA < valB) return -1; | ||
if (valA > valB) return 1; | ||
return 0; | ||
}; | ||
return arr.slice().sort(compare); | ||
}; | ||
export const groupBy = function(array, property) { | ||
return array.reduce((result, obj) => { | ||
const key = get(obj, property); | ||
if (key !== undefined) { | ||
if (!result[key]) { | ||
result[key] = []; | ||
} | ||
result[key].push(obj); | ||
} | ||
return result; | ||
}, {}); | ||
} | ||
/*------------------- | ||
@@ -435,3 +490,3 @@ Objects | ||
export const pick = function (obj, ...keys) { | ||
export const pick = (obj, ...keys) => { | ||
let copy = {}; | ||
@@ -446,30 +501,84 @@ each(keys, function (key) { | ||
export const arrayFromObject = (obj) => { | ||
if(isArray(obj)) { | ||
return obj; | ||
} | ||
let arr = []; | ||
each(obj, (value, key) => { | ||
arr.push({ | ||
value, | ||
key, | ||
}); | ||
}); | ||
return arr; | ||
}; | ||
/* | ||
Access a nested object field from a string, like 'a.b.c' | ||
*/ | ||
export const get = function (obj, path = '') { | ||
if (typeof path !== 'string') { | ||
return undefined; | ||
} | ||
// map 'foo.1.baz' -> foo[1].baz' | ||
const transformArrayRegExp = /\[(\w+)\]/g; | ||
function extractArrayLikeAccess(part) { | ||
const key = part.substring(0, part.indexOf('[')); | ||
const index = parseInt(part.substring(part.indexOf('[') + 1, part.indexOf(']')), 10); | ||
return { key, index }; | ||
} | ||
export const get = function (obj, string = '') { | ||
if (typeof string !== 'string') { | ||
function getCombinedKey(path) { | ||
const dotIndex = path.indexOf('.'); | ||
if (dotIndex !== -1) { | ||
const nextDotIndex = path.indexOf('.', dotIndex + 1); | ||
if (nextDotIndex !== -1) { | ||
return path.slice(0, nextDotIndex); | ||
} | ||
} | ||
return path; | ||
} | ||
if (obj === null || !isObject(obj)) { | ||
return undefined; | ||
} | ||
// Transform array notation to dot notation | ||
const stringParts = string.replace(transformArrayRegExp, '.$1').split('.'); | ||
const parts = path.split('.'); | ||
let currentObject = obj; | ||
for (let index = 0; index < stringParts.length; index++) { | ||
const part = stringParts[index]; | ||
if (part === '') continue; // Skip empty parts that result from leading dots or consecutive dots. | ||
// Check if the part exists in the current object | ||
if (currentObject !== null && typeof currentObject === 'object' && part in currentObject) { | ||
currentObject = currentObject[part]; | ||
} else { | ||
// If the path breaks, return undefined | ||
for (let i = 0; i < parts.length; i++) { | ||
if (currentObject === null || !isObject(currentObject)) { | ||
return undefined; | ||
} | ||
let part = parts[i]; | ||
if (part.includes('[')) { | ||
const { key, index } = extractArrayLikeAccess(part); | ||
if (key in currentObject && isArray(currentObject[key]) && index < currentObject[key].length) { | ||
currentObject = currentObject[key][index]; | ||
} else { | ||
return undefined; | ||
} | ||
} else { | ||
if (part in currentObject) { | ||
currentObject = currentObject[part]; | ||
} else { | ||
const remainingPath = parts.slice(i).join('.'); | ||
if (remainingPath in currentObject) { | ||
currentObject = currentObject[remainingPath]; | ||
break; | ||
} else { | ||
const combinedKey = getCombinedKey(`${part}.${parts[i + 1]}`); | ||
if (combinedKey in currentObject) { | ||
currentObject = currentObject[combinedKey]; | ||
i++; | ||
} else { | ||
return undefined; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
return currentObject; | ||
@@ -675,6 +784,22 @@ }; | ||
*/ | ||
export const escapeRegExp = function (string) { | ||
export const escapeRegExp = (string) => { | ||
return string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1'); | ||
}; | ||
export const escapeHTML = (string) => { | ||
const htmlEscapes = { | ||
'&': '&', | ||
'<': '<', | ||
'>': '>', | ||
'"': '"', | ||
"\'": ''' | ||
}; | ||
const htmlRegExp = /[&<>"']/g; | ||
const hasHTML = RegExp(htmlRegExp.source); | ||
return (string && hasHTML.test(string)) | ||
? string.replace(htmlRegExp, (chr) => htmlEscapes[chr]) | ||
: string | ||
; | ||
}; | ||
/*------------------- | ||
@@ -685,4 +810,5 @@ Identity | ||
export const tokenize = (str = '') => { | ||
return (str).replace(/\s+/g, '-') | ||
return (str || '').replace(/\s+/g, '-') | ||
.replace(/[^\w-]+/g, '') | ||
.replace(/_/g, '-') | ||
.toLowerCase() | ||
@@ -689,0 +815,0 @@ ; |
@@ -1,5 +0,15 @@ | ||
import { describe, expect, it, vi } from 'vitest'; | ||
import { clone } from '@semantic-ui/utils'; | ||
import { describe, expect, beforeEach, afterEach, afterAll, beforeAll, it, vi } from 'vitest'; | ||
import { clone, copyText, getText, isDOM, fatal } from '@semantic-ui/utils'; | ||
beforeAll(() => { | ||
vi.spyOn(console, 'error').mockImplementation((...args) => { | ||
throw new Error(`Unhandled Console Error: ${args.join(' ')}`); | ||
}); | ||
}); | ||
afterAll(() => { | ||
console.error.mockRestore(); | ||
}); | ||
describe('clone', () => { | ||
@@ -13,2 +23,98 @@ | ||
}); | ||
}); | ||
describe('copyText', () => { | ||
it('should call navigator.clipboard.writeText with the provided text', () => { | ||
const writeTextMock = vi.fn(); | ||
global.navigator = { | ||
clipboard: { | ||
writeText: writeTextMock, | ||
}, | ||
}; | ||
const text = 'Test text'; | ||
copyText(text); | ||
expect(writeTextMock).toHaveBeenCalledWith(text); | ||
}); | ||
}); | ||
describe('getText', () => { | ||
it('should fetch the text content from the provided source', async () => { | ||
const mockResponse = 'Test text'; | ||
global.fetch = vi.fn().mockResolvedValue({ | ||
text: () => Promise.resolve(mockResponse), | ||
}); | ||
const src = 'https://example.com/test.txt'; | ||
const result = await getText(src); | ||
expect(fetch).toHaveBeenCalledWith(src); | ||
expect(result).toBe(mockResponse); | ||
}); | ||
}); | ||
describe('isDOM', () => { | ||
it('should return true for Element instances', () => { | ||
const element = document.createElement('div'); | ||
expect(isDOM(element)).toBe(true); | ||
}); | ||
it('should return true for Document instances', () => { | ||
expect(isDOM(document)).toBe(true); | ||
}); | ||
it('should return true for window', () => { | ||
expect(isDOM(window)).toBe(true); | ||
}); | ||
it('should return true for DocumentFragment instances', () => { | ||
const fragment = document.createDocumentFragment(); | ||
expect(isDOM(fragment)).toBe(true); | ||
}); | ||
}); | ||
/* Need to fix this | ||
describe('fatal', () => { | ||
let originalOnError; | ||
beforeEach(() => { | ||
originalOnError = global.onError; | ||
global.onError = vi.fn(); | ||
}); | ||
afterEach(() => { | ||
global.onError = originalOnError; | ||
}); | ||
it('should throw an error with the provided message', async () => { | ||
await expect(new Promise((resolve, reject) => { | ||
try { | ||
fatal('Test error', { onError: reject }); | ||
} catch (error) { | ||
reject(error); | ||
} | ||
})).rejects.toThrow('Test error'); | ||
}); | ||
it('should attach provided metadata to the error', async () => { | ||
const metadata = { code: 'ERR_TEST' }; | ||
await expect(new Promise((resolve, reject) => { | ||
try { | ||
fatal('Test error', { metadata, onError: reject }); | ||
} catch (error) { | ||
reject(error); | ||
} | ||
})).rejects.toHaveProperty('code', 'ERR_TEST'); | ||
}); | ||
it('should modify the error stack based on removeStackLines option', async () => { | ||
await expect(new Promise((resolve, reject) => { | ||
try { | ||
fatal('Test error', { removeStackLines: 2, onError: reject }); | ||
} catch (error) { | ||
reject(error); | ||
} | ||
})).rejects.toSatisfy((error) => { | ||
return error.stack.split('\n').length < new Error().stack.split('\n').length; | ||
}); | ||
}); | ||
}); | ||
*/ |
import { describe, expect, it, vi } from 'vitest'; | ||
import { each, clone, unique, filterEmpty, last, firstMatch, findIndex, remove, inArray, range, keys, values, mapObject, extend, pick, get, hasProperty, reverseKeys, isObject, isPlainObject, isString, isNumber, isArray, isBinary, isFunction, isPromise, isArguments, formatDate, noop, wrapFunction, kebabToCamel, camelToKebab, capitalizeWords, toTitleCase, escapeRegExp, prettifyID, hashCode, generateID, isEqual, fatal } from '@semantic-ui/utils'; | ||
import { tokenize, | ||
asyncEach, | ||
asyncMap, | ||
camelToKebab, | ||
capitalizeWords, | ||
clone, | ||
each, | ||
escapeHTML, | ||
escapeRegExp, | ||
extend, | ||
filterEmpty, | ||
findIndex, | ||
firstMatch, | ||
formatDate, | ||
generateID, | ||
get, | ||
groupBy, | ||
hashCode, | ||
hasProperty, | ||
inArray, | ||
isArguments, | ||
isArray, | ||
isBinary, | ||
isEqual, | ||
isFunction, | ||
isNumber, | ||
isObject, | ||
isPlainObject, | ||
isPromise, | ||
isString, | ||
kebabToCamel, | ||
keys, | ||
last, | ||
mapObject, | ||
noop, | ||
pick, | ||
prettifyID, | ||
range, | ||
remove, | ||
reverseKeys, | ||
sortBy, | ||
toTitleCase, | ||
tokenize, | ||
unique, | ||
values, | ||
where, | ||
wrapFunction | ||
} from '@semantic-ui/utils'; | ||
@@ -57,2 +104,17 @@ describe('Array Utilities', () => { | ||
describe('where', () => { | ||
it('should filter an array of objects based on properties', () => { | ||
const array = [ | ||
{ id: 1, name: 'John' }, | ||
{ id: 2, name: 'Jane' }, | ||
{ id: 3, name: 'John' }, | ||
]; | ||
const result = where(array, { name: 'John' }); | ||
expect(result).toEqual([ | ||
{ id: 1, name: 'John' }, | ||
{ id: 3, name: 'John' }, | ||
]); | ||
}); | ||
}); | ||
}); | ||
@@ -119,3 +181,115 @@ | ||
}); | ||
describe('groupBy', () => { | ||
it('should group objects by a simple property', () => { | ||
const array = [ | ||
{ name: 'Alice', age: 25 }, | ||
{ name: 'Bob', age: 30 }, | ||
{ name: 'Charlie', age: 25 }, | ||
]; | ||
const expected = { | ||
'25': [ | ||
{ name: 'Alice', age: 25 }, | ||
{ name: 'Charlie', age: 25 }, | ||
], | ||
'30': [ | ||
{ name: 'Bob', age: 30 }, | ||
], | ||
}; | ||
expect(groupBy(array, 'age')).toEqual(expected); | ||
}); | ||
it('should group objects by a nested property', () => { | ||
const array = [ | ||
{ name: 'Alice', details: { city: 'New York' } }, | ||
{ name: 'Bob', details: { city: 'London' } }, | ||
{ name: 'Charlie', details: { city: 'New York' } }, | ||
]; | ||
const expected = { | ||
'New York': [ | ||
{ name: 'Alice', details: { city: 'New York' } }, | ||
{ name: 'Charlie', details: { city: 'New York' } }, | ||
], | ||
'London': [ | ||
{ name: 'Bob', details: { city: 'London' } }, | ||
], | ||
}; | ||
expect(groupBy(array, 'details.city')).toEqual(expected); | ||
}); | ||
it('should handle an empty array', () => { | ||
const array = []; | ||
const expected = {}; | ||
expect(groupBy(array, 'age')).toEqual(expected); | ||
}); | ||
it('should handle objects with missing property', () => { | ||
const array = [ | ||
{ name: 'Alice', age: 25 }, | ||
{ name: 'Bob' }, | ||
{ name: 'Charlie', age: 30 }, | ||
]; | ||
const expected = { | ||
'25': [ | ||
{ name: 'Alice', age: 25 }, | ||
], | ||
'30': [ | ||
{ name: 'Charlie', age: 30 }, | ||
], | ||
}; | ||
expect(groupBy(array, 'age')).toEqual(expected); | ||
}); | ||
it('should handle a property that does not exist on any objects', () => { | ||
const array = [ | ||
{ name: 'Alice', age: 25 }, | ||
{ name: 'Bob', age: 30 }, | ||
{ name: 'Charlie', age: 25 }, | ||
]; | ||
const expected = {}; | ||
expect(groupBy(array, 'city')).toEqual(expected); | ||
}); | ||
}); | ||
describe('sortBy', () => { | ||
it('should sort by a simple key', () => { | ||
const input = [{a: 2}, {a: 3}, {a: 1}]; | ||
const expected = [{a: 1}, {a: 2}, {a: 3}]; | ||
expect(sortBy(input, 'a')).toEqual(expected); | ||
}); | ||
it('should sort by a nested key', () => { | ||
const input = [{a: {b: 2}}, {a: {b: 3}}, {a: {b: 1}}]; | ||
const expected = [{a: {b: 1}}, {a: {b: 2}}, {a: {b: 3}}]; | ||
expect(sortBy(input, 'a.b')).toEqual(expected); | ||
}); | ||
it('should handle custom comparator', () => { | ||
const input = [{a: 1}, {a: 2}, {a: 3}]; | ||
const expected = [{a: 3}, {a: 2}, {a: 1}]; | ||
const reverseComparator = (a, b) => b - a; | ||
expect(sortBy(input, 'a', reverseComparator)).toEqual(expected); | ||
}); | ||
it('should handle sorting with additional object context in comparator', () => { | ||
const input = [{a: 1, b: 2}, {a: 1, b: 1}]; | ||
const expected = [{a: 1, b: 1}, {a: 1, b: 2}]; | ||
const comparator = (valA, valB, objA, objB) => objA.b - objB.b; | ||
expect(sortBy(input, 'a', comparator)).toEqual(expected); | ||
}); | ||
it('should return a new array and not mutate the original', () => { | ||
const input = [{a: 1}, {a: 2}]; | ||
const result = sortBy(input, 'a'); | ||
expect(result).not.toBe(input); | ||
expect(result).toEqual([{a: 1}, {a: 2}]); | ||
}); | ||
it('should sort objects with undefined values last', () => { | ||
const input = [{a: 1}, {a: undefined}, {a: 2}]; | ||
const expected = [{a: 1}, {a: 2}, {a: undefined}]; | ||
expect(sortBy(input, 'a')).toEqual(expected); | ||
}); | ||
}); | ||
}); | ||
@@ -134,57 +308,110 @@ | ||
it('keys should return the keys of an object', () => { | ||
expect(keys({ a: 1, b: 2 })).toEqual(['a', 'b']); | ||
expect(keys([1, 2, 3])).toEqual(['0', '1', '2']); | ||
describe('keys', () => { | ||
it('keys should return the keys of an object', () => { | ||
expect(keys({ a: 1, b: 2 })).toEqual(['a', 'b']); | ||
expect(keys([1, 2, 3])).toEqual(['0', '1', '2']); | ||
}); | ||
}); | ||
it('values should return the values of an object', () => { | ||
expect(values({ a: 1, b: 2 })).toEqual([1, 2]); | ||
expect(values([1, 2, 3])).toEqual([1, 2, 3]); | ||
describe('values', () => { | ||
it('values should return the values of an object', () => { | ||
expect(values({ a: 1, b: 2 })).toEqual([1, 2]); | ||
expect(values([1, 2, 3])).toEqual([1, 2, 3]); | ||
}); | ||
}); | ||
it('mapObject should create an object with the same keys and mapped values', () => { | ||
const result = mapObject({ a: 1, b: 2 }, (val) => val * 2); | ||
expect(result).toEqual({ a: 2, b: 4 }); | ||
describe('mapObject', () => { | ||
it('mapObject should create an object with the same keys and mapped values', () => { | ||
const result = mapObject({ a: 1, b: 2 }, (val) => val * 2); | ||
expect(result).toEqual({ a: 2, b: 4 }); | ||
}); | ||
}); | ||
it('extend should merge properties from source into target, including getters and setters', () => { | ||
const target = { a: 1 }; | ||
const source = { | ||
get b() { return 2; }, | ||
set b(val) { this.c = val; } | ||
}; | ||
extend(target, source); | ||
expect(target.b).toBe(2); | ||
target.b = 3; | ||
expect(target.c).toBe(3); | ||
describe('extend', () => { | ||
it('extend should merge properties from source into target, including getters and setters', () => { | ||
const target = { a: 1 }; | ||
const source = { | ||
get b() { return 2; }, | ||
set b(val) { this.c = val; } | ||
}; | ||
extend(target, source); | ||
expect(target.b).toBe(2); | ||
target.b = 3; | ||
expect(target.c).toBe(3); | ||
}); | ||
}); | ||
it('pick should create an object composed of the picked properties', () => { | ||
const obj = { a: 1, b: 2, c: 3 }; | ||
expect(pick(obj, 'a', 'c')).toEqual({ a: 1, c: 3 }); | ||
describe('pick', () => { | ||
it('pick should create an object composed of the picked properties', () => { | ||
const obj = { a: 1, b: 2, c: 3 }; | ||
expect(pick(obj, 'a', 'c')).toEqual({ a: 1, c: 3 }); | ||
}); | ||
}); | ||
it('get should access a nested object field from a string', () => { | ||
const obj = { a: { b: { c: 1 } } }; | ||
expect(get(obj, 'a.b.c')).toBe(1); | ||
expect(get(obj, 'a.b.c.d')).toBeUndefined(); | ||
}); | ||
describe('get', () => { | ||
it('hasProperty should return true if the object has the specified property', () => { | ||
const obj = { a: 1, b: undefined }; | ||
expect(hasProperty(obj, 'a')).toBe(true); | ||
expect(hasProperty(obj, 'b')).toBe(true); | ||
expect(hasProperty(obj, 'c')).toBe(false); | ||
it('get should support array like "arr.1.value" notation in lookup', () => { | ||
const obj = { arr: [{ value: 1 }, { value: 2 }] }; | ||
expect(get(obj, 'arr.1.value')).toBe(2); | ||
}); | ||
it('get should access a nested object field from a string', () => { | ||
const obj = { a: { b: { c: 1 } } }; | ||
expect(get(obj, 'a.b.c')).toBe(1); | ||
expect(get(obj, 'a.b.c.d')).toBeUndefined(); | ||
}); | ||
it('get should support files with "." in the key', () => { | ||
const obj = { 'a.b': 1 }; | ||
expect(get(obj, 'a.b')).toBe(1); | ||
}); | ||
it('get should support deeply nested files with "." in the key', () => { | ||
const obj = { a: { 'b.c': 1 } }; | ||
expect(get(obj, 'a.b.c')).toBe(1); | ||
}); | ||
it('get should return undefined when accessing a non-existent nested key', () => { | ||
const obj = { a: { b: { c: 1 } } }; | ||
expect(get(obj, 'a.b.d')).toBeUndefined(); | ||
}); | ||
it('get should support accessing nested keys with dots and array indexes', () => { | ||
const obj = { 'a.b': [{ 'c.d': 1 }, { 'c.d': 2 }] }; | ||
expect(get(obj, 'a.b.1.c.d')).toBe(2); | ||
}); | ||
it('get should return undefined when accessing an out-of-bounds array index', () => { | ||
const obj = { arr: [1, 2, 3] }; | ||
expect(get(obj, 'arr.3')).toBeUndefined(); | ||
}); | ||
it('get should return undefined when accessing a non-existent array index', () => { | ||
const obj = { a: { b: [1, 2, 3] } }; | ||
expect(get(obj, 'a.c.1')).toBeUndefined(); | ||
}); | ||
}); | ||
it('reverseKeys should reverse a lookup object\'s keys and values', () => { | ||
const obj = { a: 1, b: [1, 2], c: 2 }; | ||
const reversed = reverseKeys(obj); | ||
expect(reversed).toEqual({ '1': ['a', 'b'], '2': ['b', 'c'] }); | ||
describe('hasProperty', () => { | ||
it('hasProperty should return true if the object has the specified property', () => { | ||
const obj = { a: 1, b: undefined }; | ||
expect(hasProperty(obj, 'a')).toBe(true); | ||
expect(hasProperty(obj, 'b')).toBe(true); | ||
expect(hasProperty(obj, 'c')).toBe(false); | ||
}); | ||
}); | ||
it('reverseKeys should reverse array values', () => { | ||
const obj = { a: [1, 2], b: [2, 3], c: 1 }; | ||
const reversed = reverseKeys(obj); | ||
expect(reversed).toEqual({ '1': ['a', 'c'], '2': ['a', 'b'], '3': 'b' }); | ||
describe('reverseKeys', () => { | ||
it('reverseKeys should reverse a lookup object\'s keys and values', () => { | ||
const obj = { a: 1, b: [1, 2], c: 2 }; | ||
const reversed = reverseKeys(obj); | ||
expect(reversed).toEqual({ '1': ['a', 'b'], '2': ['b', 'c'] }); | ||
}); | ||
it('reverseKeys should reverse array values', () => { | ||
const obj = { a: [1, 2], b: [2, 3], c: 1 }; | ||
const reversed = reverseKeys(obj); | ||
expect(reversed).toEqual({ '1': ['a', 'c'], '2': ['a', 'b'], '3': 'b' }); | ||
}); | ||
}); | ||
@@ -380,2 +607,10 @@ | ||
describe('tokenize', () => { | ||
it('should convert a string to a token', () => { | ||
expect(tokenize('Hello World')).toBe('hello-world'); | ||
expect(tokenize('A simple-test_string')).toBe('a-simple-test-string'); | ||
}); | ||
}); | ||
describe('prettifyID', () => { | ||
@@ -522,16 +757,97 @@ it('should return "0" for input 0', () => { | ||
describe('regular expression utilities', () => { | ||
it('should escape characters that have special meaning in regex', () => { | ||
const specialChars = '. * + ? ^ $ { } ( ) | [ ] \\'; | ||
const escaped = escapeRegExp(specialChars); | ||
expect(() => new RegExp(escaped)).not.toThrow(); | ||
describe('escapeRegExp', () => { | ||
it('should escape characters that have special meaning in regex', () => { | ||
const specialChars = '. * + ? ^ $ { } ( ) | [ ] \\'; | ||
const escaped = escapeRegExp(specialChars); | ||
expect(() => new RegExp(escaped)).not.toThrow(); | ||
}); | ||
}); | ||
describe('escapeHTML', () => { | ||
it('should escape only HTML tag characters', () => { | ||
const input = '<div>Hello "World"</div>'; | ||
const expected = '<div>Hello "World"</div>'; | ||
expect(escapeHTML(input)).toBe(expected); | ||
}); | ||
it('should not modify a string without special characters', () => { | ||
const input = 'Hello World'; | ||
expect(escapeHTML(input)).toBe(input); | ||
}); | ||
}); | ||
}); | ||
describe('each iterator utility', () => { | ||
describe('iterators', () => { | ||
describe('Array iteration', () => { | ||
it('should iterate over all elements', () => { | ||
describe('each', () => { | ||
describe('Array iteration', () => { | ||
it('should iterate over all elements', () => { | ||
const array = [1, 2, 3]; | ||
const spy = vi.fn(); | ||
each(array, spy); | ||
expect(spy).toHaveBeenCalledTimes(3); | ||
expect(spy).toHaveBeenNthCalledWith(1, 1, 0, array); | ||
expect(spy).toHaveBeenNthCalledWith(2, 2, 1, array); | ||
expect(spy).toHaveBeenNthCalledWith(3, 3, 2, array); | ||
}); | ||
it('should break early if the callback returns false', () => { | ||
const array = [1, 2, 3]; | ||
const spy = vi.fn().mockReturnValueOnce(true).mockReturnValueOnce(false); | ||
each(array, spy); | ||
expect(spy).toHaveBeenCalledTimes(2); | ||
}); | ||
}); | ||
describe('Object iteration', () => { | ||
it('should iterate over all properties', () => { | ||
const obj = { a: 1, b: 2, c: 3 }; | ||
const spy = vi.fn(); | ||
each(obj, spy); | ||
expect(spy).toHaveBeenCalledTimes(3); | ||
expect(spy).toHaveBeenCalledWith(1, 'a', obj); | ||
expect(spy).toHaveBeenCalledWith(2, 'b', obj); | ||
expect(spy).toHaveBeenCalledWith(3, 'c', obj); | ||
}); | ||
it('should break early if the callback returns false', () => { | ||
const obj = { a: 1, b: 2, c: 3 }; | ||
const spy = vi.fn().mockReturnValueOnce(true).mockReturnValueOnce(false); | ||
each(obj, spy); | ||
expect(spy).toHaveBeenCalledTimes(2); | ||
}); | ||
it('should not iterate over non-enumerable properties', () => { | ||
const obj = { a: 1, b: 2 }; | ||
Object.defineProperty(obj, 'c', { | ||
value: 3, | ||
enumerable: false, | ||
}); | ||
const spy = vi.fn(); | ||
each(obj, spy); | ||
expect(spy).toHaveBeenCalledTimes(2); // should not include non-enumerable 'c' | ||
expect(spy).toHaveBeenCalledWith(1, 'a', obj); | ||
expect(spy).toHaveBeenCalledWith(2, 'b', obj); | ||
expect(spy).not.toHaveBeenCalledWith(3, 'c', obj); // ensure 'c' is not iterated | ||
}); | ||
}); | ||
it('should handle null/undefined gracefully', () => { | ||
const spy = vi.fn(); | ||
each(null, spy); | ||
each(undefined, spy); | ||
expect(spy).not.toHaveBeenCalled(); | ||
}); | ||
}); | ||
describe('asyncEach', () => { | ||
it('should iterate over an array asynchronously', async () => { | ||
const array = [1, 2, 3]; | ||
const spy = vi.fn(); | ||
each(array, spy); | ||
await asyncEach(array, spy); | ||
expect(spy).toHaveBeenCalledTimes(3); | ||
@@ -543,51 +859,31 @@ expect(spy).toHaveBeenNthCalledWith(1, 1, 0, array); | ||
it('should break early if the callback returns false', () => { | ||
const array = [1, 2, 3]; | ||
const spy = vi.fn().mockReturnValueOnce(true).mockReturnValueOnce(false); | ||
each(array, spy); | ||
expect(spy).toHaveBeenCalledTimes(2); | ||
it('should handle null/undefined gracefully', async () => { | ||
const spy = vi.fn(); | ||
await asyncEach(null, spy); | ||
await asyncEach(undefined, spy); | ||
expect(spy).not.toHaveBeenCalled(); | ||
}); | ||
}); | ||
describe('Object iteration', () => { | ||
it('should iterate over all properties', () => { | ||
const obj = { a: 1, b: 2, c: 3 }; | ||
const spy = vi.fn(); | ||
each(obj, spy); | ||
expect(spy).toHaveBeenCalledTimes(3); | ||
expect(spy).toHaveBeenCalledWith(1, 'a', obj); | ||
expect(spy).toHaveBeenCalledWith(2, 'b', obj); | ||
expect(spy).toHaveBeenCalledWith(3, 'c', obj); | ||
describe('asyncMap', () => { | ||
it('should map an array asynchronously', async () => { | ||
const array = [1, 2, 3]; | ||
const result = await asyncMap(array, async (value) => value * 2); | ||
expect(result).toEqual([2, 4, 6]); | ||
}); | ||
it('should break early if the callback returns false', () => { | ||
it('should map an object asynchronously', async () => { | ||
const obj = { a: 1, b: 2, c: 3 }; | ||
const spy = vi.fn().mockReturnValueOnce(true).mockReturnValueOnce(false); | ||
each(obj, spy); | ||
expect(spy).toHaveBeenCalledTimes(2); | ||
const result = await asyncMap(obj, async (value) => value * 2); | ||
expect(result).toEqual({ a: 2, b: 4, c: 6 }); | ||
}); | ||
it('should not iterate over non-enumerable properties', () => { | ||
const obj = { a: 1, b: 2 }; | ||
Object.defineProperty(obj, 'c', { | ||
value: 3, | ||
enumerable: false, | ||
}); | ||
const spy = vi.fn(); | ||
each(obj, spy); | ||
expect(spy).toHaveBeenCalledTimes(2); // should not include non-enumerable 'c' | ||
expect(spy).toHaveBeenCalledWith(1, 'a', obj); | ||
expect(spy).toHaveBeenCalledWith(2, 'b', obj); | ||
expect(spy).not.toHaveBeenCalledWith(3, 'c', obj); // ensure 'c' is not iterated | ||
it('should handle null/undefined gracefully', async () => { | ||
const result1 = await asyncMap(null, async (value) => value); | ||
expect(result1).toBeNull(); | ||
const result2 = await asyncMap(undefined, async (value) => value); | ||
expect(result2).toBeUndefined(); | ||
}); | ||
}); | ||
it('should handle null/undefined gracefully', () => { | ||
const spy = vi.fn(); | ||
each(null, spy); | ||
each(undefined, spy); | ||
expect(spy).not.toHaveBeenCalled(); | ||
}); | ||
}); |
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
59526
1726
102
2