diff --git a/__tests__/experiments/isBlank/isBlank.js b/__tests__/experiments/isBlank/isBlank.js new file mode 100644 index 0000000..c914e47 --- /dev/null +++ b/__tests__/experiments/isBlank/isBlank.js @@ -0,0 +1,28 @@ +// ! Example Tester Idea from https://www.30secondsofcode.org/js/s/blank-value/ +// * Should Basically Do The Same Thing As is_empty underlaying function from src/is_empty.js +// ? Nested things may give false positives compared to "is_empty" and especially +// ? when compared to "is_empty_nested" function. + +const isFalsy = (value) => !value + +const isWhitespaceString = (value) => typeof value === 'string' && /^\s*$/.test(value) + +const isEmptyCollection = (value) => (Array.isArray(value) || value === Object(value)) && !Object.keys(value).length + +const isInvalidDate = (value) => value instanceof Date && Number.isNaN(value.getTime()) + +const isEmptySet = (value) => value instanceof Set && value.size === 0 + +const isEmptyMap = (value) => value instanceof Map && value.size === 0 + +const isBlank = (value) => { + if (isFalsy(value)) return true + if (isWhitespaceString(value)) return true + if (isEmptyCollection(value)) return true + if (isInvalidDate(value)) return true + if (isEmptySet(value)) return true + if (isEmptyMap(value)) return true + return false +} + +module.exports = isBlank diff --git a/__tests__/experiments/isBlank/samples/index.js b/__tests__/experiments/isBlank/samples/index.js new file mode 100644 index 0000000..a6b6f83 --- /dev/null +++ b/__tests__/experiments/isBlank/samples/index.js @@ -0,0 +1,78 @@ +// ! Example Tester Idea from https://www.30secondsofcode.org/js/s/blank-value/ +// * Should Basically Do The Same Thing As is_empty underlaying function from src/is_empty.js +// ? Nested things may give false positives compared to "is_empty" and especially +// ? when compared to "is_empty_nested" function. + +const isFalsy = (value) => !value +const isWhitespaceString = (value) => typeof value === 'string' && /^\s*$/.test(value) +const isEmptyCollection = (value) => (Array.isArray(value) || value === Object(value)) && !Object.keys(value).length +const isInvalidDate = (value) => value instanceof Date && Number.isNaN(value.getTime()) +const isEmptySet = (value) => value instanceof Set && value.size === 0 +const isEmptyMap = (value) => value instanceof Map && value.size === 0 + +const isBlank = (value) => { + if (isEmptySet(value)) return true + if (isEmptyMap(value)) return true + if (isInvalidDate(value)) return true + if (isWhitespaceString(value)) return true + if (isEmptyCollection(value)) return true + if (isFalsy(value)) return true + return false +} + +console.log(isBlank(null)) // true +console.log(isBlank(undefined)) // true +console.log(isBlank(0)) // true +console.log(isBlank(false)) // true +console.log(isBlank('')) // true +console.log(isBlank(' \r\n ')) // true +console.log(isBlank(NaN)) // true + +const cLog = console.log + +// +// #01.001 : Empty array +const arrayEmpty = [] +cLog(`arrayEmpty | ${isBlank(arrayEmpty)} === true | [SLV]: ${arrayEmpty.length}`) // true 0 +// #01.002 : (technically) Not empty array +const arraySomething = [[]] +cLog(`arraySomething | ${isBlank(arraySomething)} === false | [SLV]: ${arraySomething.length}`) // false 1 + +// +// #02.001 : Object that is empty +const objEmpty = {} +cLog(`objEmpty | ${isBlank(objEmpty)} === true | [SLV]: ${objEmpty}`) // true +// #02.002 : Object that is not empty +const objNotEmpty = { v: 11, t: 'ok' } +cLog(`objNotEmpty | ${isBlank(objNotEmpty)} === false | [SLV]: ${objNotEmpty}`) // false + +// +// #03.001 : Date() that is invalid +const dateInvalide = new Date('hello') +cLog(`dateInvalide | ${isBlank(dateInvalide)} === true | [SLV]: ${dateInvalide}`) // true +// #03.002 : Date() that is valid +const dateValid = new Date() +cLog(`dateValid | ${isBlank(dateValid)} === false | [SLV]: ${dateValid}`) // false + +// +// #04.001 : Set() that is empty +const demoSetEmpty = new Set() +cLog(`demoSetEmpty | ${isBlank(demoSetEmpty)} === true | [SLV]: ${demoSetEmpty.size}`) // true +// #04.002 : Set() that with a value +const demoSet = new Set([1, 2, 3]) +cLog(`demoSet | ${isBlank(demoSet)} === false | [SLV]: ${demoSet.size}`) // false 3 + +// +// #05.001 : Map() that is empty +const demoMapEmpty = new Map() +cLog(`demoMapEmpty | ${isBlank(demoMapEmpty)} === true | [SLV]: ${demoMapEmpty.size}`) // true +// #05.002 : Map() that with a value +const demoMap = new Map() +demoMap.set('a', 0) +cLog(`demoMap | ${isBlank(demoMap)} === false | [SLV]: ${demoMap.size}`) // false 1 +// #05.003 : Map() constructor can take an array of key-value pairs +const demoMap2 = new Map([ + ['a', 0], + ['b', 1] +]) +cLog(`demoMap2 | ${isBlank(demoMap2)} === false | [SLV]: ${demoMap2.size}`) // false 2 diff --git a/__tests__/experiments/isBlank/tryouts/001.js b/__tests__/experiments/isBlank/tryouts/001.js new file mode 100644 index 0000000..5453986 --- /dev/null +++ b/__tests__/experiments/isBlank/tryouts/001.js @@ -0,0 +1,83 @@ +const isBlank = require('../isBlank') +const { isEmpty } = require('../../../..') + +console.assert(typeof isBlank === 'function', 'isBlank should be a function') + +const demoMap = new Map([['a', 0]]) +const demoEmptyMap = new Map() + +const demoSet = new Set([1, 2, 3, 4, 5]) +const demoEmptySet = new Set() + +const dateInvalid = new Date('hello') +const dateValid = new Date() + +const arrayEmpty = [] +const arraySomething = [0] + +const objEmpty = {} +const objNotEmpty = { v: 11, t: 'ok' } + +const testCases = [ + [null, true, 'null >> true', true], + [undefined, true, 'undefined >> true', true], + [NaN, true, 'NaN >> true', false], + + [0, true, '0 >> true', false], + [1, false, '1 >> false', false], + + [false, true, 'false >> true', false], + [true, false, 'true >> false', false], + + ['', true, '"" >> true', true], + [' \r\n ', true, '" \r\n " >> true', true], + ['hello', false, '"hello" >> false', false], + + [arrayEmpty, true, '[] >> true', true], + [arraySomething, false, '[0] >> false', false], + + [objEmpty, true, '{} >> true', true], + [objNotEmpty, false, '{ a: 0 } >> false', false], + + // TODO: 🔁 RETHINK this test case + // NOTE: Rethink the problem since this falls into "v_rifier" category of problems. + [dateInvalid, true, 'new Date("hello") >> true', false], // true], + [dateValid, false, 'new Date() >> false', false], + + [demoEmptySet, true, 'new Set() >> true', true], + // TODO: 🚩 Fix this test case for "isEmpty()" + // NOTE: Set with a value is not empty + [demoSet, false, 'new Set([1, 2, 3, 4, 5]) >> false', false], + + [demoEmptyMap, true, 'new Map() >> true', true], + // TODO: 🚩 Fix this test case for "isEmpty()" + // NOTE: Map with a value is not empty + [demoMap, false, 'new Map([["a", 0]]) >> false', false] +] + +const printInput = (input) => + !!input && input instanceof Date && input['toISOString'] !== undefined && !Number.isNaN(input.getTime()) + ? input.toISOString() + : JSON.stringify(input) + +console.log(`\n[ isBlank ]>------------------------------------`) + +let i = 0 +testCases.forEach(([input, expected, info]) => { + console.assert(isBlank(input) === expected, `#${i} | ${info} : isBlank(${printInput(input)}) should be ${expected}`) + i++ +}) +console.log(`-------------------------------------\n`) + +console.log(`[ isEmpty ]>------------------------------------`) + +i = 0 +testCases.forEach(([input, expected, info, expectedIsEmpty]) => { + console.assert( + isEmpty(input) === expectedIsEmpty, + `#${i} | ${info} : isEmpty(${printInput(input)}) should be ${expectedIsEmpty}` + ) + i++ +}) + +console.log(`-------------------------------------\n`) diff --git a/src/constants.js b/src/constants.js index 52e30af..de93c0d 100644 --- a/src/constants.js +++ b/src/constants.js @@ -1,8 +1,14 @@ +export const knownInstances = ['Date', 'Promise', 'Error', 'Boolean', 'Number'] + /** * Checks if a value is an instance of a specific type. * * @param {*} val - The value to check. * @returns {boolean} - Returns true if the value is an instance of a specific type, otherwise returns false. */ -export const isInstance = (val) => ['Date', 'Promise', 'Error', 'Boolean', 'Number'].indexOf(val) !== -1 +export const isKnownInstance = (val) => knownInstances.indexOf(val) !== -1 + +export const noKeys = (obj) => Object.keys(obj).length === 0 + +export const notNullObject = (value) => typeof value === 'object' && value !== null diff --git a/src/dev/is_empty-v2.js b/src/dev/is_empty-v2.js index c3c83d3..94af641 100644 --- a/src/dev/is_empty-v2.js +++ b/src/dev/is_empty-v2.js @@ -1,4 +1,4 @@ -import { isInstance } from './constants' +import { isKnownInstance, noKeys, notNullObject } from '../constants' /** * Checks if a value is empty. @@ -7,8 +7,8 @@ import { isInstance } from './constants' * @returns {boolean} - Returns true if the value is empty, otherwise false. */ const is_empty = (value) => { - if (isInstance(value?.constructor?.name)) return false - return typeof value === 'object' && value !== null ? Object.keys(value).length === 0 : !value + if (isKnownInstance(value?.constructor?.name)) return false + return notNullObject(value) ? noKeys(value) : !value } export default is_empty diff --git a/src/dev/is_empty.nested-v2.js b/src/dev/is_empty.nested-v2.js index 6c9d386..5cde86e 100644 --- a/src/dev/is_empty.nested-v2.js +++ b/src/dev/is_empty.nested-v2.js @@ -1,4 +1,4 @@ -import { isInstance } from './constants' +import { isKnownInstance, notNullObject } from './constants' /** * Checks if a nested value is empty. @@ -6,11 +6,11 @@ import { isInstance } from './constants' * @returns {boolean} - Returns true if the value is empty, otherwise false. */ const is_empty_nested = (value) => { - if (isInstance(value?.constructor?.name)) return false + if (isKnownInstance(value?.constructor?.name)) return false if (Array.isArray(value)) return value.length > 0 ? value.every((item) => is_empty_nested(item)) : true - if (typeof value === 'object' && value !== null) { + if (notNullObject(value)) { const keys = Object.keys(value) return keys.length > 0 ? keys.every((key) => Object.prototype.hasOwnProperty.call(value, key) && is_empty_nested(value[key])) diff --git a/src/index.js b/src/index.js index 0e6910c..42c366c 100644 --- a/src/index.js +++ b/src/index.js @@ -11,7 +11,15 @@ import is_empty_nested from './is_empty.nested' * @param {*} v - The value to check. * @returns {boolean} - Returns `true` if the value is empty, `false` otherwise. */ -export const isEmpty = (v) => is_empty(v) +export const isEmpty = is_empty + +/** + * Checks if a nested value is empty. + * @function isEmptyNested + * @param {*} v - The nested value to check. + * @returns {boolean} - Returns `true` if the nested value is empty, `false` otherwise. + */ +export const isEmptyNested = is_empty_nested /** * Checks if a value is not empty. @@ -21,14 +29,6 @@ export const isEmpty = (v) => is_empty(v) */ export const isNotEmpty = (v) => !is_empty(v) -/** - * Checks if a nested value is empty. - * @function isEmptyNested - * @param {*} v - The nested value to check. - * @returns {boolean} - Returns `true` if the nested value is empty, `false` otherwise. - */ -export const isEmptyNested = (v) => is_empty_nested(v) - /** * Checks if a nested value is not empty. * @function isNotEmptyNested @@ -37,5 +37,3 @@ export const isEmptyNested = (v) => is_empty_nested(v) */ export const isNotEmptyNested = (v) => !is_empty_nested(v) -export { is_empty, is_empty_nested } - diff --git a/src/is_empty.js b/src/is_empty.js index bd986a3..4bf23f2 100644 --- a/src/is_empty.js +++ b/src/is_empty.js @@ -1,4 +1,4 @@ -import { isInstance } from './constants' +import { isKnownInstance, noKeys, notNullObject } from './constants' /** * Checks if a value is empty. @@ -6,15 +6,11 @@ import { isInstance } from './constants' * @param {*} value - The value to check. * @returns {boolean} - Returns true if the value is empty, otherwise false. */ -const is_empty = (value) => { +export default function is_empty(value) { if (value === undefined) return true - if (isInstance(value?.constructor?.name)) return false + if (isKnownInstance(value?.constructor?.name)) return false - if (typeof value === 'object' && value !== null) return Object.keys(value).length === 0 - - return !value + return notNullObject(value) ? noKeys(value) : !value } -export default is_empty - diff --git a/src/is_empty.nested.js b/src/is_empty.nested.js index aeb847b..f295d99 100644 --- a/src/is_empty.nested.js +++ b/src/is_empty.nested.js @@ -1,14 +1,14 @@ -import { isInstance } from './constants' +import { isKnownInstance, notNullObject } from './constants' /** * Checks if a nested value is empty. * @param {*} value - The value to check. * @returns {boolean} - Returns true if the value is empty, otherwise false. */ -const is_empty_nested = (value) => { +export default function is_empty_nested(value) { if (value === undefined) return true - if (isInstance(value?.constructor?.name)) return false + if (isKnownInstance(value?.constructor?.name)) return false if (Array.isArray(value)) { if (value.length === 0) return true @@ -20,7 +20,7 @@ const is_empty_nested = (value) => { return true } - if (typeof value === 'object' && value !== null) { + if (notNullObject(value)) { const keys = Object.keys(value) if (keys.length === 0) return true let i = 0 @@ -35,5 +35,3 @@ const is_empty_nested = (value) => { return !value } -export default is_empty_nested -