diff --git a/packages/common/test/utils/shared.utils.spec.ts b/packages/common/test/utils/shared.utils.spec.ts index 96c4f1978..e52068202 100644 --- a/packages/common/test/utils/shared.utils.spec.ts +++ b/packages/common/test/utils/shared.utils.spec.ts @@ -26,7 +26,13 @@ describe('Shared utils', () => { it('should return false when object is not undefined', () => { expect(isUndefined({})).toBe(false); }); + it('should return false for falsy values like false, 0, or empty string', () => { + expect(isUndefined(false)).to.be.false; + expect(isUndefined(0)).to.be.false; + expect(isUndefined('')).to.be.false; + }); }); + describe('isFunction', () => { it('should return true when obj is function', () => { expect(isFunction(() => ({}))).toBe(true); @@ -36,16 +42,21 @@ describe('Shared utils', () => { expect(isFunction(undefined)).toBe(false); }); }); + describe('isObject', () => { it('should return true when obj is object', () => { expect(isObject({})).toBe(true); }); + it('should return true for arrays', () => { + expect(isObject([1, 2, 3])).to.be.true; // Arrays are objects + }); it('should return false when object is not object', () => { expect(isObject(3)).toBe(false); expect(isObject(null)).toBe(false); expect(isObject(undefined)).toBe(false); }); }); + describe('isPlainObject', () => { it('should return true when obj is plain object', () => { expect(isPlainObject({})).toBe(true); @@ -65,7 +76,12 @@ describe('Shared utils', () => { expect(isPlainObject(new Date())).toBe(false); expect(isPlainObject(new Foo(1))).toBe(false); }); + it('should return false for objects with custom prototypes', () => { + function CustomObject() {} + expect(isPlainObject(new CustomObject())).to.be.false; + }); }); + describe('isString', () => { it('should return true when val is a string', () => { expect(isString('true')).toBe(true); @@ -77,6 +93,7 @@ describe('Shared utils', () => { expect(isString(undefined)).toBe(false); }); }); + describe('isSymbol', () => { it('should return true when val is a Symbol', () => { expect(isSymbol(Symbol())).toBe(true); @@ -87,7 +104,11 @@ describe('Shared utils', () => { expect(isSymbol(null)).toBe(false); expect(isSymbol(undefined)).toBe(false); }); + it('should return false for invalid Symbol objects', () => { + expect(isSymbol(Object(Symbol()))).to.be.false; + }); }); + describe('isNumber', () => { it('should return true when val is a number or NaN', () => { expect(isNumber(1)).toBe(true); @@ -97,14 +118,18 @@ describe('Shared utils', () => { expect(isNumber(0b1)).toBe(true); // binary notation expect(isNumber(0x1)).toBe(true); // hexadecimal notation expect(isNumber(NaN)).toBe(true); + expect(isNumber(Infinity)).toBe(true); + expect(isNumber(-Infinity)).toBe(true); }); it('should return false when val is not a number', () => { // expect(isNumber(1n)).toBe(false); // big int (available on ES2020) expect(isNumber('1')).toBe(false); // string expect(isNumber(undefined)).toBe(false); // nullish expect(isNumber(null)).toBe(false); // nullish + expect(isNumber(new Number(123))).toBe(false); // number }); }); + describe('isConstructor', () => { it('should return true when string is equal to constructor', () => { expect(isConstructor('constructor')).toBe(true); @@ -112,7 +137,13 @@ describe('Shared utils', () => { it('should return false when string is not equal to constructor', () => { expect(isConstructor('nope')).toBe(false); }); + it('should return false for non-string values', () => { + expect(isConstructor(null)).to.be.false; + expect(isConstructor(undefined)).to.be.false; + expect(isConstructor(123)).to.be.false; + }); }); + describe('addLeadingSlash', () => { it('should return the validated path ("add / if not exists")', () => { expect(addLeadingSlash('nope')).toEqual('/nope'); @@ -127,7 +158,13 @@ describe('Shared utils', () => { expect(addLeadingSlash(null!)).toEqual(''); expect(addLeadingSlash(undefined)).toEqual(''); }); + it('should handle paths with special characters', () => { + expect(addLeadingSlash('path-with-special-chars!@#$%^&*()')).to.eql( + '/path-with-special-chars!@#$%^&*()', + ); + }); }); + describe('normalizePath', () => { it('should remove all trailing slashes at the end of the path', () => { expect(normalizePath('path/')).toEqual('/path'); @@ -145,6 +182,7 @@ describe('Shared utils', () => { expect(normalizePath(undefined)).toEqual('/'); }); }); + describe('isNil', () => { it('should return true when obj is undefined or null', () => { expect(isNil(undefined)).toBe(true); @@ -153,7 +191,13 @@ describe('Shared utils', () => { it('should return false when object is not undefined and null', () => { expect(isNil('3')).toBe(false); }); + it('should return false for falsy values like false, 0, or empty string', () => { + expect(isNil(false)).to.be.false; + expect(isNil(0)).to.be.false; + expect(isNil('')).to.be.false; + }); }); + describe('isEmpty', () => { it('should return true when array is empty or not exists', () => { expect(isEmpty([])).toBe(true); @@ -163,10 +207,19 @@ describe('Shared utils', () => { it('should return false when array is not empty', () => { expect(isEmpty([1, 2])).toBe(false); }); + it('should return true for non-array values', () => { + expect(isEmpty({})).to.be.true; + expect(isEmpty('')).to.be.true; + expect(isEmpty(0)).to.be.true; + expect(isEmpty(false)).to.be.true; + }); }); + describe('stripEndSlash', () => { it('should strip end slash if present', () => { expect(stripEndSlash('/cats/')).toBe('/cats'); + }); + it('should return the same path if no trailing slash exists', () => { expect(stripEndSlash('/cats')).toBe('/cats'); }); }); diff --git a/packages/common/utils/shared.utils.ts b/packages/common/utils/shared.utils.ts index e936c8d24..170a32335 100644 --- a/packages/common/utils/shared.utils.ts +++ b/packages/common/utils/shared.utils.ts @@ -1,10 +1,10 @@ -export const isUndefined = (obj: any): obj is undefined => +export const isUndefined = (obj: unknown): obj is undefined => typeof obj === 'undefined'; -export const isObject = (fn: any): fn is object => +export const isObject = (fn: unknown): fn is object => !isNil(fn) && typeof fn === 'object'; -export const isPlainObject = (fn: any): fn is object => { +export const isPlainObject = (fn: unknown): fn is object => { if (!isObject(fn)) { return false; } @@ -37,15 +37,25 @@ export const normalizePath = (path?: string): string => : '/' + path.replace(/\/+$/, '') : '/'; -export const stripEndSlash = (path: string) => - path[path.length - 1] === '/' ? path.slice(0, path.length - 1) : path; +export const stripEndSlash = (path: string): string => + path.endsWith('/') ? path.slice(0, -1) : path; -export const isFunction = (val: any): val is Function => +export const isFunction = (val: unknown): val is Function => typeof val === 'function'; -export const isString = (val: any): val is string => typeof val === 'string'; -export const isNumber = (val: any): val is number => typeof val === 'number'; -export const isConstructor = (val: any): boolean => val === 'constructor'; -export const isNil = (val: any): val is null | undefined => + +export const isString = (val: unknown): val is string => + typeof val === 'string'; + +export const isNumber = (val: unknown): val is number => + typeof val === 'number'; + +export const isConstructor = (val: unknown): boolean => val === 'constructor'; + +export const isNil = (val: unknown): val is null | undefined => isUndefined(val) || val === null; -export const isEmpty = (array: any): boolean => !(array && array.length > 0); -export const isSymbol = (val: any): val is symbol => typeof val === 'symbol'; + +export const isEmpty = (array: unknown): boolean => + !(Array.isArray(array) && array.length > 0); + +export const isSymbol = (val: unknown): val is symbol => + typeof val === 'symbol';