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 | Arraya\
" => 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); +});