diff --git a/README.md b/README.md index 63f23f8..0868350 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,19 @@ # AceLords Node Utils Common utils for your node/js projects +### Why this library? +This libray focuses more on presentation aspects, not to be used in business logic. +Different frameworks/libraries render `null`, `undefined` and `0` differently +> NB e.g. in svelte, `null` is rendered as `"null"` in this template `{functionThatReturnsNull(null)}` and as such would be such a hasle writing multiple `#if` for checking `null` (or even `{functionThatReturnsNull(null) ?? ""}`). It would be nice to just have `{functionThatReturnsNullAsString(undefined)}` for improved `DX` don't you agree? + +Most functions are meant to be uses only in the `Presentation layer`. Use them sparingly in the `Business logic layer`. + +#### Sample results from the docs on hover +![Screenshot 1](./src/utils-screenshot-1.png) + +#### Tests for quick reference +![Screenshot 2](./src/utils-screenshot-2.png) + ## Installation Install via `npm` @@ -12,13 +25,13 @@ pnpm i @acelords/js-utils ## Docs Available functions. [View the entire list here](https://github.com/acelords/node-utils/blob/main/src/index.ts) +- formatDate() - formatDateTime() - getTimeFromDate() - randomNumber() - randomString() - isNumeric() - fromNow() -- ucwords() - substring() - numberFormat() - formatNumber() @@ -29,7 +42,58 @@ Available functions. [View the entire list here](https://github.com/acelords/nod - singular() - pluralize() - insertIntoArray() +- getMonthNameFromSqlMonthIndex() - isPhoneNumber() +- isEmail() +- isEmpty() +- ucwords() +- capitalize() +- camelCaseToSentenceCase() +- snakeCaseToSentenceCase() +- kebabCaseToSentenceCase() +- kebabCaseToPascalCase() +- kebabCase() +- scrollToTop() +- countWords() +- countWordsFromHtml() +- birthdayFromNow() +- isPhoneNumber() + + +## Sample Usages +- Format a number value in human-readable way:
+ `

Clicked {formatNumber(978345, true)} times.

`
+ `

Clicked 978,345 times.

`
+ `

Clicked {formatNumber(null|undefined|"", true)} times.

`
+ `

Clicked times.

`
+ `

Clicked {formatNumber(null|undefined|"" ?? 0, true)} times.

`
+ `

Clicked 0 times.

` + `

Clicked {formatNumber(null|undefined|"" ?? "0", true)} times.

`
+ `

Clicked 0 times.

`
+ `

Clicked {formatNumber(null|undefined|"" ?? "1000", true)} times.

`
+ `

Clicked 1,000 times.

`
+ `

Clicked {formatNumber("abc", true)} times.

`
+ `

Clicked times.

` + +- Format currency saved in cents (as you should) in human-readable way (`Int|BigInt|Float` also supported):
+ `

Costs ${formatCurrency(132949)} only.

`
+ `

Costs $1,329.49 only.

`
+ `

Costs ${formatCurrency(null|undefined|"")} only.

`
+ `

Costs $ only.

`
+ `

Costs ${formatCurrency(null|undefined|"" ?? "0")} only.

`
+ `

Costs $0.00 only.

`
+ `

Costs ${formatCurrency(null|undefined|"" ?? 0)} only.

`
+ `

Costs $0.00 only.

`
+ `

Costs ${formatCurrency(null|undefined|"" ?? 1099)} only.

`
+ `

Costs $10.99 only.

`
+ `

Costs ${formatCurrency("abc")} only.

`
+ `

Costs only.

` + +- Count apples sold by the doctor:
+ `

You have sold {formatNumber(3454, true)} {pluralize('apples', applesCount)} today.

`
+ `

You have sold 3,454 apples today.

`
+ `

You have sold 1 apple today.

`
+ `

You have sold 0 apples today.

` ## Dev Notes diff --git a/package.json b/package.json index bff1757..402a95a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@acelords/js-utils", - "version": "1.0.7", + "version": "1.0.8", "description": "Common utils and helpers used on node projects", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/index.ts b/src/index.ts index 0a15153..11413a8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,9 +8,11 @@ const MIN_TIMESTAMP = 75600000; const MIN_DATE = 'January 2, 1970'; /** - * format a date - * date => 22nd Jun 2021, 2:00 pm + * format a date. Default format: "D MMM, YYYY" * https://day.js.org/docs/en/display/format + * - date => 8 Oct, 2020 + * - null|undefined => "" + * - 1602162242 => 8 Oct, 2020 */ export const formatDate = (dt: Date | string | number | null | undefined, format = 'D MMM, YYYY'): string => { if (!dt) return ""; @@ -23,7 +25,9 @@ export const formatDate = (dt: Date | string | number | null | undefined, format } /** -* format a date and include time by default + * format a date and include time by default. Default format: "D MMM, YYYY hh:mm a" + * - date => 8 Oct, 2020 1:04 pm + * - 1602162242 => 8 Oct, 2020 1:04 pm */ export const formatDateTime = (dt: Date | string | number | null | undefined, format = 'D MMM, YYYY hh:mm a'): string => { if (!dt) return ""; @@ -36,7 +40,9 @@ export const formatDateTime = (dt: Date | string | number | null | undefined, fo } /** - * return 24-hour time from an ISO 8601 date + * return 24-hour time from an ISO 8601 date. Default format: "HH:mm" + * - date => 21:04 + * - 1692803186 => 16:04 */ export const getTimeFromDate = (dt: string | number | Date, format = "HH:mm"): string => { if (isNumeric(dt.toString())) { @@ -48,8 +54,9 @@ export const getTimeFromDate = (dt: string | number | Date, format = "HH:mm"): s }; /** -* generate a random number, min inclusive, max NOT inclusive -* - Math.random() -> a number from 0 to <1 + * generate a random number, min inclusive, max NOT inclusive + * - NB: Math.random() ->> a number from 0 to <1 + * - 10,80 returns 10 <= x < 80 */ export const randomNumber = (min = 0, max = 10000) => { return Math.floor(Math.random() * (max - min)) + min; @@ -75,6 +82,7 @@ export const randomString = (length = 6, includeNumbers = false) => { /** + * Check if a string is numeric * - isNumeric('abcd') // false * - isNumeric('123a') // false * - isNumeric('1') // true @@ -93,23 +101,23 @@ export function isNumeric(value: string | number | undefined | null): boolean { } /** - * check how long a date is from now + * Check how long a date is from now * - date => 2 days ago + * - date => in 2 days + * - null|undefined => "" */ -export const fromNow = (date: Date | string | null | undefined, addSuffix = undefined) => { +export const fromNow = (date: Date | string | null | undefined, addSuffix = undefined): string => { if (!date) return ""; if (!dayjs(date).isValid()) return "" return dayjs(date).fromNow(addSuffix); }; /** - * Capitalize first word in a string. - * Does NOT lowerCase the rest - */ -export const ucwords = (str: string): string => str.substring(0, 1).toUpperCase() + str.substring(1) - -/** - * get a substring of a string + * Get a substring of a string + * - abcdef, 1 => a + * - abcd, 4 => abcd + * - abc, 10 => abc + * - null|undefined => "" */ export const substring = (str: string | null | undefined, end: number): string => str ? str.substring(0, end) : '' @@ -117,11 +125,18 @@ export const substring = (str: string | null | undefined, end: number): string = * format a number to 2dp. * - 1000 becomes 1,000.00. * - If toInt=true, 1000000 becomes 1,000,000. + * - "ab78" => "" + * - null|undefined => "" + * - "0" => "0.00" + * - 0 => "0.00" + * - "0", true => "0" + * - 0, true => "0" * - Displaying other groupings/separators is possible, look at the docs http://numeraljs.com/ */ export const numberFormat = (value: string | number | undefined | null, toInt = false): string => { - if (!value || !isNumeric(value)) return "" const format = toInt ? '0,0' : "0,0.00" + if (value === 0 || value === "0") return numeral(value).format(format) + if (!value || !isNumeric(value)) return "" return numeral(value).format(format) } @@ -130,6 +145,12 @@ export const numberFormat = (value: string | number | undefined | null, toInt = * format a number to 2dp. * - 1000 becomes 1,000.00. * - If toInt=true, 1000000 becomes 1,000,000. + * - "ab78" => "" + * - null|undefined => "" + * - "0" => "0.00" + * - 0 => "0.00" + * - "0", true => "0" + * - 0, true => "0" * - Displaying other groupings/separators is possible, look at the docs http://numeraljs.com/ */ export const formatNumber = (value: string | number | undefined | null, toInt = false): string => { @@ -138,25 +159,39 @@ export const formatNumber = (value: string | number | undefined | null, toInt = /** * format currency. Value passed must be in cents. - * - 1500000 becomes 15,000.00 - * - 123456 becomes 1,234.56 + * - 1500000 => 15,000.00 + * - 123456 => 1,234.56 + * - "0" => "0.00" + * - 0 => "0.00" */ export const formatCurrency = (value: string | number | undefined | null): string => { + const format = "0,0.00" + if (value === 0 || value === "0") return numeral(0).format(format) if (!value || !isNumeric(value)) return "" const amount = Number(value.toString()) / 100; - return numeral(amount).format("0,0.00") + return numeral(amount).format(format) } /** * slugify a string. - * - iwef k[wef #mgt% becomes iwef-kwef-mgt + * - iwef k[wef #mgt% => iwef-kwef-mgt + * - iwef âẽèéë eded => iwef-aeeee-eded + * - iwef .--/,;: âẽèéë .--/,;: eded => iwef-aeeee-eded */ export const slugify = (str: string | null | undefined): string => { if (!str) return '' - return str + str = str .toLowerCase() - .trim() - .replace(/[^\w\s-]/g, '') + .trim(); + + // remove accents, swap ñ for n, etc + var from = "ãàáäâẽèéëêìíïîõòóöôùúüûñç·/_,:;"; + var to = "aaaaaeeeeeiiiiooooouuuunc------"; + for (var i = 0, l = from.length; i < l; i++) { + str = str.replace(new RegExp(from.charAt(i), "g"), to.charAt(i)); + } + + return str.replace(/[^\w\s-]/g, '') .replace(/[\s_-]+/g, '-') .replace(/^-+|-+$/g, ''); } @@ -166,7 +201,7 @@ export const slugify = (str: string | null | undefined): string => { * @param htmlString string * @returns string */ -export const stripTags = (htmlString: string) => { +export const stripTags = (htmlString: string): string => { return htmlString.replaceAll(/(<([^>]+)>)/gi, '').replace(/]*>/gi, ''); } @@ -240,6 +275,11 @@ export function getMonthNameFromSqlMonthIndex(index: number, format = 'MMM') { /** * check if a string is a phoneNumber. * This is a loose check. For advanced use-cases, use the intl package + * - 123456789 => true + * - +123456789 => true + * - 0123456789 => true + * - p123456789 => false + * - ~123456789 => false */ export const isPhoneNumber = (str: string) => { str = str @@ -253,3 +293,247 @@ export const isPhoneNumber = (str: string) => { return !isNaN(Number(str)); }; + + +/** + * check if email is valide + * - info@acelords.com => true + * - infoacelords.com => false + * - info@acelordscom => false + * - +123456789 => false + * - "" | null | undefined => false + */ +export function isEmail(emailAdress: string | null | undefined): boolean { + if (!emailAdress) return false; + + let regex = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/; + + if (emailAdress.match(regex)) + return true; + + return false; +} + +/** + * Check if string, array or object is empty + * - null|undefined => true + * - "." => false + * - "" => true + * - [] => true + * - [1] | ["k"] => false + * - {} => true + * - {a:4} => false + */ +export const isEmpty = (value: string | null | undefined | object | Array): boolean => { + if (!value) return true; + + if (typeof value === 'object') + return Object.keys(value).length < 1; + + if (Array.isArray(value)) + return value.length < 1; + + return value.length < 1; +}; + + +/** + * Capitalize first word in a string. + * Does NOT lowerCase the rest + * - 'abcd efg' => 'Abcd efg' + * - ' abcd efg ' => 'Abcd efg' + */ +export const ucwords = (str: string | null | undefined): string => (str || "").trim().substring(0, 1).toUpperCase() + (str || "").trim().substring(1) + +/** + * capitalize words in a string + * - 'abcd efg' => 'Abcd Efg' + * - ' abcd efg ' => 'Abcd Efg' + */ +export const capitalize = (value: string | null | undefined): string => { + if (!value) return ""; + return (value.replace(/(?:^|\s)\S/g, function (a) { + return a.toUpperCase(); + })).trim(); +}; + +/** + * Convert camel case to sentence case + * - yourStringIsDope => Your string is dope + * - yourStringIsDope, true => Your String Is Dope + * - abc456 => Abc456 + */ +export const camelCaseToSentenceCase = (value: string | null | undefined, capitalizeWords = false): string => { + if (!value) return ""; + value = (value.replace(/([A-Z])/g, " $1")).trim(); + return capitalizeWords + ? value.charAt(0).toUpperCase() + value.slice(1) + : value.charAt(0).toUpperCase() + value.slice(1).toLowerCase() +}; + + +/** + * Convert snake case to sentence case + * - 'my_string_is_dope' => 'My string is dope' + * - 'my_string_is_dope', true => 'My String Is Dope' + * - 123_456 => 123 456 + * - abc_efg_456 => Abc efg 456 + * - abc_efg_456, true => Abc Efg 456 + * - abc_456_efg => Abc 456 efg + * - abc_456_efg, true => Abc 456 Efg + */ +export const snakeCaseToSentenceCase = (value: string | null | undefined, capitalizeWords = false): string => { + if (!value) return ""; + value = value.replace(/(_)/g, " "); + value = value.replace(/([A-Z])/g, " $1"); + return capitalizeWords + ? value.replace(/\b\w/g, function (l) { + return l.toUpperCase(); + }) + : value.charAt(0).toUpperCase() + value.slice(1); // capitalize the first letter - as an example. +}; + + +/** + * Convert kebab case to sentence case, capitalizing all words + * - 'your-string-is-dope' => 'Your string is dope' + * - 'your-string-is-dope', true => 'Your String Is Dope' + * - 'abcd' => 'abcd' + * - 'abcd', true => 'Abcd' + * - 123-456 => 123 456 + * - abc-efg-456 => Abc efg 456 + * - abc-efg-456, true => Abc Efg 456 + * - abc-456-efg => Abc 456 efg + * - abc-456-efg, true => Abc 456 Efg + */ +export const kebabCaseToSentenceCase = (value: string | null | undefined, capitalizeWords = false): string => { + if (!value) return ""; + value = value.replace(/-([a-z])/g, function (g) { + return (g[1] ?? "").toUpperCase(); + }); + // separate numbers + value = value.replace(/-([0-9])/g, function (g) { + return ` ${(g[1] ?? "")}` + }); + value = (value.replace(/([A-Z])/g, " $1")).toLowerCase(); + + return capitalizeWords + ? capitalize(value) + : ucwords(value); +}; + +/** + * Convert kebab-case to PascalCase + * - 'your-string' => 'YourString' + * - my-string-is-dope => MyStringIsDope + * - 123-456-efg => 123456Efg + */ +export const kebabCaseToPascalCase = (value: string | null | undefined): string => { + if (!value) return ""; + value = value.replace(/-([a-z])/g, function (g) { + return (g[1] ?? "").toUpperCase(); + }); + // handle numbers + value = value.replace(/-([0-9])/g, function (g) { + return `${(g[1] ?? "")}` + }); + value = value.replace(/([A-Z])/g, "$1"); + return value.replace(/\b\w/g, function (l) { + return l.toUpperCase(); + }); +}; + +/** + * Convert a string to kebab. The string is trimmed first + * - abcd => abcd + * - aBcD => a-bc-d + * - aB-cD => a-b-c-d + * - my string => my-string + * - my-string-is-dope => my-string-is-dope + * - 123 456 Efg => 123-456-efg + * - 123 456 Efg => 123-456-efg + */ +export const kebabCase = (value: string | null | undefined): string => { + if (!value) return ""; + + return value + .trim() + .replace(/([a-z])([A-Z])/g, "$1-$2") // get all lowercase letters that are near to uppercase ones + .replace(/[\s_]+/g, "-") // replace all spaces and low dash + .toLowerCase() +}; + +/** + * Utility to scroll to top in a smooth behaviour + */ +export const scrollToTop = (topOffset = 100) => { + window.scrollTo({ + top: topOffset, + behavior: "smooth", + }); +}; + +/** + * Count words in a string + * - "" => 0 + * - "a" => 1 + * - "a b" => 2 + * - "a-b" => 1 + * - null|undefined => 0 + */ +export const countWords = (str: string | null | undefined): number => { + if (!str) return 0; + return str.split(" ").filter(function (n) { + return n != ""; + }).length; +}; + +/** + * Count words from a html string + * - "" => 0 + * - "a" => 1 + * - "a b" => 2 + * - "a-b" => 1 + * - null|undefined => 0 + * - "\

a\

" => 1 + * - \

a b\

=> 2 + * - \

a-b\

=> 1 + * - \

\f\a b\

=> 3 + */ +export const countWordsFromHtml = (s: string | null | undefined): number => { + if (!s) return 0; + s = s.replace(/<\/?[^>]+(>|$)/g, " "); // strip tags + s = s.replace(/[.]{2,}/gi, " "); // 2 or more fullstops to 1 + s = s.replace(/[ ]{2,}/gi, " "); // 2 or more space to 1 + s = s.replace(/(^\s*)|(\s*$)/gi, ""); // exclude start and end white-space + s = s.replace(/\n /, " "); // exclude newline with a start spacing + return s.split(" ").filter(function (str) { + return str != ""; + }).length; +}; + + +/** + * Get birthday + * - Date(today - 4 days) => 361 (362 if leap year) + * - Date(today + 4 days) => 3 + * - Date(today) => 0 + * - null|undefined => null + */ +export const birthdayFromNow = (date: Date | string | null | undefined): number | null => { + if (!date) return null; + if (!dayjs(date).isValid()) return null + + let birthday = new Date(date); + + const today = new Date(); + + // Set current year or the next year if you already had birthday this year + birthday.setFullYear(today.getFullYear()); + if (today > birthday) { + birthday.setFullYear(today.getFullYear() + 1); + } + + // Calculate difference between days + return Math.floor((birthday.getTime() - today.getTime()) / (1000 * 60 * 60 * 24)); +}; diff --git a/src/utils-screenshot-1.png b/src/utils-screenshot-1.png new file mode 100644 index 0000000..f4def5a Binary files /dev/null and b/src/utils-screenshot-1.png differ diff --git a/src/utils-screenshot-2.png b/src/utils-screenshot-2.png new file mode 100644 index 0000000..8f0b4a2 Binary files /dev/null and b/src/utils-screenshot-2.png differ diff --git a/src/utils.test.ts b/src/utils.test.ts index 20555b1..ba397ee 100644 --- a/src/utils.test.ts +++ b/src/utils.test.ts @@ -1,4 +1,4 @@ -import { formatCurrency, formatDate, formatDateTime, formatNumber, fromNow, getTimeFromDate, insertIntoArray, isNumeric, isPhoneNumber, numberFormat, plural, pluralize, randomNumber, randomString, singular, slugify, stripTags, substring, ucwords } from './index' +import { birthdayFromNow, camelCaseToSentenceCase, capitalize, countWords, countWordsFromHtml, formatCurrency, formatDate, formatDateTime, formatNumber, fromNow, getTimeFromDate, insertIntoArray, isEmail, isEmpty, isNumeric, isPhoneNumber, kebabCase, kebabCaseToPascalCase, kebabCaseToSentenceCase, numberFormat, plural, pluralize, randomNumber, randomString, singular, slugify, snakeCaseToSentenceCase, stripTags, substring, ucwords } from './index' import dayjs from 'dayjs'; /*======== formatDate =============*/ @@ -138,13 +138,6 @@ test("fromNow - can get the time fromNow", () => { expect(fromNow(dt)).toBe("in a day"); }); -/*======== ucwords =============*/ -test("ucwords - capitalizes first word in a string", () => { - expect(ucwords('abcd')).toBe("Abcd"); - expect(ucwords('abcd efgh')).toBe("Abcd efgh"); - expect(ucwords('ABCD')).toBe("ABCD"); - expect(ucwords('aBCD')).toBe("ABCD"); -}); /*======== substring =============*/ test("substring - can get a substring of a string", () => { @@ -161,6 +154,10 @@ test("numberFormat - can format numeric strings and numbers", () => { expect(numberFormat('abcd')).toBe(""); expect(numberFormat(null)).toBe(""); expect(numberFormat(undefined)).toBe(""); + expect(numberFormat("0")).toBe("0.00"); + expect(numberFormat(0)).toBe("0.00"); + expect(numberFormat("0", true)).toBe("0"); + expect(numberFormat(0, true)).toBe("0"); expect(numberFormat('123456')).toBe("123,456.00"); expect(numberFormat('123456', true)).toBe("123,456"); }); @@ -169,6 +166,10 @@ test("formatNumber - can format numeric strings and numbers", () => { expect(formatNumber('abcd')).toBe(""); expect(formatNumber(null)).toBe(""); expect(formatNumber(undefined)).toBe(""); + expect(formatNumber("0")).toBe("0.00"); + expect(formatNumber(0)).toBe("0.00"); + expect(formatNumber("0", true)).toBe("0"); + expect(formatNumber(0, true)).toBe("0"); expect(formatNumber('123456')).toBe("123,456.00"); expect(formatNumber('123456', true)).toBe("123,456"); }); @@ -178,6 +179,8 @@ test("formatCurrency - can format numeric strings and numbers to currency", () = expect(formatCurrency('abcd')).toBe(""); expect(formatCurrency(null)).toBe(""); expect(formatCurrency(undefined)).toBe(""); + expect(formatCurrency("0")).toBe("0.00"); + expect(formatCurrency(0)).toBe("0.00"); expect(formatCurrency('123456')).toBe("1,234.56"); expect(formatCurrency('12345600')).toBe("123,456.00"); }); @@ -189,6 +192,8 @@ test("slugify - can return a sane slug from a string", () => { expect(slugify(undefined)).toBe(""); expect(slugify('123456 abcd')).toBe("123456-abcd"); expect(slugify('iwef k[wef #mgt%')).toBe("iwef-kwef-mgt"); + expect(slugify('iwef âẽèéë eded')).toBe("iwef-aeeee-eded"); + expect(slugify('iwef .--/,;: âẽèéë .--/,;: eded')).toBe("iwef-aeeee-eded"); }); /*======== stripTags =============*/ @@ -244,3 +249,194 @@ test("isPhoneNumber - can check if a string is a phone number", () => { expect(isPhoneNumber("p123456789")).toBeFalsy(); expect(isPhoneNumber("~123456789")).toBeFalsy(); }); + +/*======== isEmail =============*/ +test("isEmail - can check if a string is an email", () => { + expect(isEmail("info@acelords.com")).toBeTruthy(); + expect(isEmail("infoacelords.com")).toBeFalsy(); + expect(isEmail("info@acelordscom")).toBeFalsy(); + expect(isEmail("+123456789")).toBeFalsy(); + expect(isEmail("")).toBeFalsy(); + expect(isEmail("p123456789")).toBeFalsy(); + expect(isEmail(null)).toBeFalsy(); + expect(isEmail(undefined)).toBeFalsy(); +}); + +/*======== isEmpty =============*/ +test("isEmpty - can check if a string, array, or object is empty", () => { + expect(isEmpty("info@acelords.com")).toBeFalsy(); + expect(isEmpty("+123456789")).toBeFalsy(); + expect(isEmpty("+")).toBeFalsy(); + expect(isEmpty(".")).toBeFalsy(); + expect(isEmpty("")).toBeTruthy(); + expect(isEmpty(null)).toBeTruthy(); + expect(isEmpty(undefined)).toBeTruthy(); + expect(isEmpty([])).toBeTruthy(); + expect(isEmpty([1])).toBeFalsy(); + expect(isEmpty(["1"])).toBeFalsy(); + expect(isEmpty({})).toBeTruthy(); + expect(isEmpty({ a: 1 })).toBeFalsy(); + expect(isEmpty({ a: "1" })).toBeFalsy(); +}); + + +/*======== ucwords =============*/ +test("ucwords - capitalizes first word in a string", () => { + expect(ucwords('abcd')).toBe("Abcd"); + expect(ucwords('abcd efgh')).toBe("Abcd efgh"); + expect(ucwords('ABCD')).toBe("ABCD"); + expect(ucwords('aBCD')).toBe("ABCD"); + expect(ucwords("0")).toBe("0"); + expect(ucwords(null)).toBe(""); + expect(ucwords(undefined)).toBe(""); + expect(ucwords(' aBCD ')).toBe("ABCD"); + expect(ucwords(' a B C D ')).toBe("A B C D"); +}); + +/*======== capitalize =============*/ +test("capitalize - capitalizes every first word in a string", () => { + expect(capitalize('abcd')).toBe("Abcd"); + expect(capitalize('abcd efgh')).toBe("Abcd Efgh"); + expect(capitalize('ABCD')).toBe("ABCD"); + expect(capitalize('aBCD')).toBe("ABCD"); + expect(capitalize("0")).toBe("0"); + expect(capitalize(null)).toBe(""); + expect(capitalize(undefined)).toBe(""); +}); + +/*======== camelCaseToSentenceCase =============*/ +test("camelCaseToSentenceCase - converts camelCase string to Sentence case", () => { + expect(camelCaseToSentenceCase('abcd')).toBe("Abcd"); + expect(camelCaseToSentenceCase('myString')).toBe("My string"); + expect(camelCaseToSentenceCase('myStringIsDope')).toBe("My string is dope"); + + expect(camelCaseToSentenceCase('abcd', true)).toBe("Abcd"); + expect(camelCaseToSentenceCase('myString', true)).toBe("My String"); + expect(camelCaseToSentenceCase('myStringIsDope', true)).toBe("My String Is Dope"); + + // tests for those who'd pass nonsense arguments + expect(camelCaseToSentenceCase('abcd efgh', true)).toBe("Abcd efgh"); + expect(camelCaseToSentenceCase('ABCD', true)).toBe("A B C D"); + expect(camelCaseToSentenceCase('aBCD', true)).toBe("A B C D"); + expect(camelCaseToSentenceCase('aBcd', true)).toBe("A Bcd"); + expect(camelCaseToSentenceCase('aBcd efg', true)).toBe("A Bcd efg"); + expect(camelCaseToSentenceCase('aBcd Efg', true)).toBe("A Bcd Efg"); + expect(camelCaseToSentenceCase("0", true)).toBe("0"); + expect(camelCaseToSentenceCase("123456")).toBe("123456"); + expect(camelCaseToSentenceCase("123 456", true)).toBe("123 456"); + expect(camelCaseToSentenceCase("abc456", true)).toBe("Abc456"); + expect(camelCaseToSentenceCase(null, true)).toBe(""); + expect(camelCaseToSentenceCase(undefined, true)).toBe(""); +}); + +/*======== snakeCaseToSentenceCase =============*/ +test("snakeCaseToSentenceCase - converts snake_case string to Sentence case", () => { + expect(snakeCaseToSentenceCase('abcd')).toBe("Abcd"); + expect(snakeCaseToSentenceCase('my_string')).toBe("My string"); + expect(snakeCaseToSentenceCase('my_string_is_dope')).toBe("My string is dope"); + + expect(snakeCaseToSentenceCase('abcd', true)).toBe("Abcd"); + expect(snakeCaseToSentenceCase('my_string', true)).toBe("My String"); + expect(snakeCaseToSentenceCase('my_string_is_dope', true)).toBe("My String Is Dope"); + + // tests for those who'd pass nonsense arguments + expect(snakeCaseToSentenceCase("0", true)).toBe("0"); + expect(snakeCaseToSentenceCase("123_456")).toBe("123 456"); + expect(snakeCaseToSentenceCase("123_456", true)).toBe("123 456"); + expect(snakeCaseToSentenceCase("abc_456")).toBe("Abc 456"); + expect(snakeCaseToSentenceCase("abc_456", true)).toBe("Abc 456"); + expect(snakeCaseToSentenceCase("abc_efg_456")).toBe("Abc efg 456"); + expect(snakeCaseToSentenceCase("abc_efg_456", true)).toBe("Abc Efg 456"); + expect(snakeCaseToSentenceCase("abc_456_efg")).toBe("Abc 456 efg"); + expect(snakeCaseToSentenceCase("abc_456_efg", true)).toBe("Abc 456 Efg"); + expect(snakeCaseToSentenceCase(null, true)).toBe(""); + expect(snakeCaseToSentenceCase(undefined, true)).toBe(""); +}); + +/*======== kebabCaseToSentenceCase =============*/ +test("kebabCaseToSentenceCase - converts kebab-case string to Sentence case", () => { + expect(kebabCaseToSentenceCase('abcd')).toBe("Abcd"); + expect(kebabCaseToSentenceCase('my-string')).toBe("My string"); + expect(kebabCaseToSentenceCase('my-string-is-dope')).toBe("My string is dope"); + expect(kebabCaseToSentenceCase('My string is dope')).toBe("My string is dope"); + + expect(kebabCaseToSentenceCase('abcd', true)).toBe("Abcd"); + expect(kebabCaseToSentenceCase('my-string', true)).toBe("My String"); + expect(kebabCaseToSentenceCase('my-string-is-dope', true)).toBe("My String Is Dope"); + expect(kebabCaseToSentenceCase('My String Is Dope', true)).toBe("My String Is Dope"); + + // tests for those who'd pass nonsense arguments + expect(kebabCaseToSentenceCase("0", true)).toBe("0"); + expect(kebabCaseToSentenceCase("123-456")).toBe("123 456"); + expect(kebabCaseToSentenceCase("123-456", true)).toBe("123 456"); + expect(kebabCaseToSentenceCase("123-456-efg")).toBe("123 456 efg"); + expect(kebabCaseToSentenceCase("123-456-efg", true)).toBe("123 456 Efg"); + expect(kebabCaseToSentenceCase(null, true)).toBe(""); + expect(kebabCaseToSentenceCase(undefined, true)).toBe(""); +}); + +/*======== kebabCaseToPascalCase =============*/ +test("kebabCaseToPascalCase - converts kebab-case string to PascalCase", () => { + expect(kebabCaseToPascalCase('abcd')).toBe("Abcd"); + expect(kebabCaseToPascalCase('my-string')).toBe("MyString"); + expect(kebabCaseToPascalCase('my-string-is-dope')).toBe("MyStringIsDope"); + expect(kebabCaseToPascalCase('MyStringIsDope')).toBe("MyStringIsDope"); + + // tests for those who'd pass nonsense arguments + expect(kebabCaseToPascalCase("0")).toBe("0"); + expect(kebabCaseToPascalCase("123-456")).toBe("123456"); + expect(kebabCaseToPascalCase("123-456-efg")).toBe("123456Efg"); + expect(kebabCaseToPascalCase(null)).toBe(""); + expect(kebabCaseToPascalCase(undefined)).toBe(""); +}); + +/*======== kebabCase =============*/ +test("kebabCase - converts string to kebab-case", () => { + expect(kebabCase('abcd')).toBe("abcd"); + expect(kebabCase('aBcD')).toBe("a-bc-d"); + expect(kebabCase('aB-cD')).toBe("a-b-c-d"); + expect(kebabCase('my string')).toBe("my-string"); + expect(kebabCase('my-string-is-dope')).toBe("my-string-is-dope"); + + // tests for those who'd pass nonsense arguments + expect(kebabCase("0")).toBe("0"); + expect(kebabCase("123 456")).toBe("123-456"); + expect(kebabCase("123 456 efg")).toBe("123-456-efg"); + expect(kebabCase("123 456 Efg")).toBe("123-456-efg"); + expect(kebabCase(" 123 456 Efg ")).toBe("123-456-efg"); + expect(kebabCase(null)).toBe(""); + expect(kebabCase(undefined)).toBe(""); +}); + +/*======== countWords =============*/ +test("countWords - can get number of words in a string", () => { + expect(countWords("")).toBe(0); + expect(countWords("a")).toBe(1); + expect(countWords("a b")).toBe(2); + expect(countWords("a-b")).toBe(1); + expect(countWords(null)).toBe(0); + expect(countWords(undefined)).toBe(0); +}); + +/*======== countWordsFromHtml =============*/ +test("countWordsFromHtml - can get number of words in a string", () => { + expect(countWordsFromHtml("")).toBe(0); + expect(countWordsFromHtml("a")).toBe(1); + expect(countWordsFromHtml("a b")).toBe(2); + expect(countWordsFromHtml("a-b")).toBe(1); + expect(countWordsFromHtml(null)).toBe(0); + expect(countWordsFromHtml(undefined)).toBe(0); + expect(countWordsFromHtml("

a

")).toBe(1); + expect(countWordsFromHtml("

a b

")).toBe(2); + expect(countWordsFromHtml("

a-b

")).toBe(1); + expect(countWordsFromHtml("

fa b

")).toBe(3); +}); + +/*======== birthdayFromNow =============*/ +test("birthdayFromNow - can get days to next birthday", () => { + expect(birthdayFromNow(dayjs('2023-08-23').subtract(4, 'days').toDate())).toBe(361); + expect(birthdayFromNow(dayjs('2023-08-23').add(4, 'days').toDate())).toBe(3); + expect(birthdayFromNow(dayjs().toDate())).toBe(0); + expect(birthdayFromNow(null)).toBe(null); + expect(birthdayFromNow(undefined)).toBe(null); +});