diff --git a/CHANGELOG.md b/CHANGELOG.md index eaaaf46..586241e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # deverything +## 1.0.0 + +### Major Changes + +- Breaking changes on args + - percentageChange + - randomInt + - randomPositiveInt + - randomNegativeInt + - randomDate + - randomMaxDate + ## 0.51.1 ### Patch Changes diff --git a/README.md b/README.md index 5010e83..ddd2600 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,8 @@ Contributions always welcome! - `max()` - `min()` - `multiply()` +- `normaliseArray()` +- `normaliseNumber()` - `percentageChange()` - `sum()` diff --git a/package.json b/package.json index c70cc85..25076ae 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "deverything", - "version": "0.51.1", + "version": "1.0.0", "description": "Everything you need for Dev", "main": "./dist/index.js", "module": "./dist/index.mjs", @@ -27,9 +27,10 @@ "checks", "dates", "fake", + "formatters", "generator", "helpers", - "formatters", + "math", "numbers", "random", "testing", diff --git a/src/formatters/formatLatin.test.ts b/src/formatters/formatLatin.test.ts new file mode 100644 index 0000000..1b9b571 --- /dev/null +++ b/src/formatters/formatLatin.test.ts @@ -0,0 +1,12 @@ +import { describe, expect, test } from "@jest/globals"; +import { formatLatin } from "./formatLatin"; + +const accentString = + "áéíóúüñ¿¡àâèêëîïôûàãàãçàèéìòóùäöüßáéíóúáéíóúąćęłńóśźżáčďéěíňóřšťúůýžćčđšžáéíóúüαβγδεζηθικλμνξοπρστυφχψωçğıİöşüابتثجحخدذرزسشصضطظعغ"; +const latinString = "aeiou"; + +describe("formatLatin", () => { + test("should return a-z", () => { + expect(formatLatin(accentString)).toBe(latinString); + }); +}); diff --git a/src/formatters/formatLatin.ts b/src/formatters/formatLatin.ts new file mode 100644 index 0000000..c346ab7 --- /dev/null +++ b/src/formatters/formatLatin.ts @@ -0,0 +1,4 @@ +export const formatLatin = (text: string): string => { + let normalized = text.normalize("NFKD"); + return normalized; +}; diff --git a/src/formatters/formatNumber.test.ts b/src/formatters/formatNumber.test.ts index e42f8d9..248446e 100644 --- a/src/formatters/formatNumber.test.ts +++ b/src/formatters/formatNumber.test.ts @@ -4,22 +4,24 @@ import { randomInt } from "../random/randomInt"; describe("formatNumber", () => { test("should return the same number if test is under a thousand", () => { - const value = randomInt(0, 999); + const value = randomInt({ min: 0, max: 999 }); expect(formatNumber(value, { compact: true })).toBe(`${value}`); }); test("should return a string in compact K notation if value is one thousand or above", () => { - const value = randomInt(1000, 9999); + const value = randomInt({ min: 1000, max: 9999 }); expect(formatNumber(value, { compact: true })).toContain("K"); }); test("should return a string in compact M notation if value is one million or above", () => { - const value = randomInt(1000000, 9999999); + const value = randomInt({ min: 1000000, max: 9999999 }); expect(formatNumber(value, { compact: true })).toContain("M"); }); test("should return a string with thousand separator but no compact notation", () => { - const formattedValue = formatNumber(randomInt(1000000, 9999999)); + const formattedValue = formatNumber( + randomInt({ min: 1000000, max: 9999999 }) + ); expect(formattedValue).not.toContain("M"); expect(formattedValue).toContain(","); }); diff --git a/src/math/index.ts b/src/math/index.ts index f4648e3..2e086cc 100644 --- a/src/math/index.ts +++ b/src/math/index.ts @@ -5,5 +5,7 @@ export * from "./isStrictlyBetween"; export * from "./max"; export * from "./min"; export * from "./multiply"; +export * from "./normaliseArray"; +export * from "./normaliseNumber"; export * from "./percentageChange"; export * from "./sum"; diff --git a/src/math/normaliseArray.ts b/src/math/normaliseArray.ts new file mode 100644 index 0000000..beffd9b --- /dev/null +++ b/src/math/normaliseArray.ts @@ -0,0 +1,14 @@ +import { max } from "./max"; +import { min } from "./min"; +import { normaliseNumber } from "./normaliseNumber"; + +/** + * Normalises an array of numbers + * @example normaliseArray([1, 2, 3]) => [0, 0.5, 1] + */ +export const normaliseArray = (values: number[]) => { + const minValue = min(values); + const maxValue = max(values); + + return values.map((value) => normaliseNumber(value, minValue, maxValue)); +}; diff --git a/src/math/normaliseNumber.ts b/src/math/normaliseNumber.ts new file mode 100644 index 0000000..e8938cc --- /dev/null +++ b/src/math/normaliseNumber.ts @@ -0,0 +1,9 @@ +/** + * + * @example normaliseNumber(50, 0, 100) => 0.5 + */ +export const normaliseNumber = ( + value: number, + minValue: number, + maxValue: number +) => (value - minValue) / (maxValue - minValue); diff --git a/src/math/percentageChange.test.ts b/src/math/percentageChange.test.ts index 8771ef5..2852342 100644 --- a/src/math/percentageChange.test.ts +++ b/src/math/percentageChange.test.ts @@ -3,29 +3,12 @@ import { percentageChange } from "./percentageChange"; describe("percentageChange", () => { test("simple", async () => { - expect( - percentageChange({ - current: 10, - previous: 12, - }) - ).toBe(-16.67); - expect( - percentageChange({ - current: 0, - previous: 12, - }) - ).toBe(0); - expect( - percentageChange({ - current: 0, - previous: 0, - }) - ).toBe(0); - expect( - percentageChange({ - current: 99, - previous: 0, - }) - ).toBe(0); + expect(percentageChange(-0.1, 0.2)).toBe(0); + expect(percentageChange(0.2, 0.1)).toBe(-0.5); + expect(percentageChange(0.1, 0.2)).toBe(1); + expect(percentageChange(0.3, 0.333)).toBe(0.11); + expect(percentageChange(0, 0.12)).toBe(0); + expect(percentageChange(0, 0)).toBe(0); + expect(percentageChange(0.99, 0)).toBe(0); }); }); diff --git a/src/math/percentageChange.ts b/src/math/percentageChange.ts index 3b5dfd5..7a300ac 100644 --- a/src/math/percentageChange.ts +++ b/src/math/percentageChange.ts @@ -1,16 +1,14 @@ -import { isPositiveInt } from "../validators"; - -export const percentageChange = ({ - previous, - current, -}: { - previous: number; - current: number; -}): number => { - if (!isPositiveInt(previous) || !isPositiveInt(current)) return 0; +/** + * + * @param previous Positive percentage i.e. 0.1 for 10% + * @param current Positive percentage i.e. 0.2 for 20% + * @returns + */ +export const percentageChange = (previous: number, current: number): number => { + if (previous < 0 || current < 0) return 0; if (current === 0 && previous === 0) return 0; - if (current === 0 && previous !== 0) return -100; - if (current !== 0 && previous === 0) return 100; - const perChange = ((current - previous) * 100) / previous; - return parseFloat(perChange.toFixed(2)); + if (current === 0 && previous !== 0) return -1; + if (current !== 0 && previous === 0) return 1; + const perChange = (current - previous) / previous; + return parseFloat(perChange.toFixed(4)); // 4 decimal places so when formatting to % two decimal places are shown }; diff --git a/src/random/randomArrayItem.ts b/src/random/randomArrayItem.ts index 9a3f987..cb810af 100644 --- a/src/random/randomArrayItem.ts +++ b/src/random/randomArrayItem.ts @@ -22,5 +22,5 @@ export const randomArrayItem = ( return last(array); } - return array[randomInt(0, lastIndex(array))]; + return array[randomInt({ min: 0, max: lastIndex(array) })]; }; diff --git a/src/random/randomBool.ts b/src/random/randomBool.ts index b33f9fe..79d5923 100644 --- a/src/random/randomBool.ts +++ b/src/random/randomBool.ts @@ -1,3 +1,3 @@ -import { randomInt } from "./randomInt"; +import { randomArrayItem } from "./randomArrayItem"; -export const randomBool = () => !!randomInt(0, 1); +export const randomBool = () => randomArrayItem([true, false]); diff --git a/src/random/randomChar.ts b/src/random/randomChar.ts index b7f835b..5c3b25d 100644 --- a/src/random/randomChar.ts +++ b/src/random/randomChar.ts @@ -1,5 +1,5 @@ import { randomInt } from "./randomInt"; export const randomChar = () => { - return String.fromCharCode(randomInt(97, 122)); + return String.fromCharCode(randomInt({ min: 97, max: 122 })); }; diff --git a/src/random/randomDate.test.ts b/src/random/randomDate.test.ts index b1fa530..7a89b80 100644 --- a/src/random/randomDate.test.ts +++ b/src/random/randomDate.test.ts @@ -6,8 +6,18 @@ describe(`randomDate`, () => { expect(randomDate().getTime()).toBeGreaterThan(0); }); it(`args`, () => { - expect(randomDate("2010", "2011").toISOString().substring(0, 3)).toBe( - "201" + expect( + randomDate({ startDate: "2010", endDate: "2011" }) + .toISOString() + .substring(0, 3) + ).toBe("201"); + }); + it(`no start`, () => { + expect(randomDate({ endDate: "2011" }).getFullYear()).toBeLessThan(2011); + }); + it(`no end`, () => { + expect(randomDate({ startDate: "2012" }).getFullYear()).toBeGreaterThan( + 2011 ); }); }); diff --git a/src/random/randomDate.ts b/src/random/randomDate.ts index e54bf6a..c6bc606 100644 --- a/src/random/randomDate.ts +++ b/src/random/randomDate.ts @@ -4,13 +4,19 @@ import { MILLISECONDS_IN_MINUTE, } from "../constants/time"; import { parseDate } from "../helpers/parseDate"; -import { DateLike, DateRange } from "../types"; +import { DateRange } from "../types"; import { isFutureDate, isPastDate } from "../validators"; import { randomInt } from "./randomInt"; -const nowPlusMs = (ms: number) => new Date(new Date().getTime() + ms); +const datePlusDecade = (date: Date = new Date()) => + datePlusMs(date, MILLISECONDS_IN_DECADE); -export const randomDate = (startDate?: DateLike, endDate?: DateLike) => { +const dateMinusDecade = (date: Date = new Date()) => + datePlusMs(date, -MILLISECONDS_IN_DECADE); + +const datePlusMs = (date: Date, ms: number) => new Date(date.getTime() + ms); + +export const randomDate = ({ startDate, endDate }: Partial = {}) => { const parsedStartDate = parseDate(startDate); const parsedEndDate = parseDate(endDate); @@ -19,24 +25,22 @@ export const randomDate = (startDate?: DateLike, endDate?: DateLike) => { } const finalStartDate = - parsedStartDate || - (parsedEndDate - ? new Date(parsedEndDate.getTime() - MILLISECONDS_IN_DECADE) - : nowPlusMs(-MILLISECONDS_IN_DECADE)); + parsedStartDate || // + dateMinusDecade(parsedEndDate); // uses now if undefined const finalEndDate = - parsedEndDate || - (parsedStartDate - ? new Date(parsedStartDate.getTime() + MILLISECONDS_IN_DECADE) - : nowPlusMs(MILLISECONDS_IN_DECADE)); + parsedEndDate || // + datePlusDecade(parsedStartDate); // uses now if undefined - return new Date(randomInt(finalStartDate.getTime(), finalEndDate.getTime())); + return new Date( + randomInt({ min: finalStartDate.getTime(), max: finalEndDate.getTime() }) + ); }; -export const randomMaxDate = (start?: Date, end?: Date) => { - const startDate = start || new Date(-MAX_DATE_MILLISECONDS); - const endDate = end || new Date(MAX_DATE_MILLISECONDS); - return randomDate(startDate, endDate); +export const randomMaxDate = ({ startDate, endDate }: Partial) => { + startDate = startDate || new Date(-MAX_DATE_MILLISECONDS); + endDate = endDate || new Date(MAX_DATE_MILLISECONDS); + return randomDate({ startDate, endDate }); }; export const randomFutureDate = ({ @@ -51,9 +55,9 @@ export const randomFutureDate = ({ } const finalStartDate = - parseDate(startDate) || nowPlusMs(5 * MILLISECONDS_IN_MINUTE); // Add a safe margin in the future (i.e. lagging tests) + parseDate(startDate) || datePlusMs(new Date(), 5 * MILLISECONDS_IN_MINUTE); // Add a safe margin in the future (i.e. lagging tests) - return randomDate(finalStartDate, endDate); + return randomDate({ startDate: finalStartDate, endDate }); }; export const randomPastDate = ({ @@ -68,12 +72,12 @@ export const randomPastDate = ({ } const finalEndDate = parseDate(endDate) || new Date(); - return randomDate(startDate, finalEndDate); + return randomDate({ startDate, endDate: finalEndDate }); }; export const randomDateRange = () => { const startDate = randomDate(); - const endDate = randomDate(startDate); + const endDate = randomDate({ startDate }); return { endDate, diff --git a/src/random/randomIP.ts b/src/random/randomIP.ts index f618ac6..a30fca5 100644 --- a/src/random/randomIP.ts +++ b/src/random/randomIP.ts @@ -2,5 +2,5 @@ import { array } from "../helpers/array"; import { randomInt } from "./randomInt"; export const randomIP = () => { - return array(4, () => randomInt(0, 255).toString()).join("."); + return array(4, () => randomInt({ min: 0, max: 255 }).toString()).join("."); }; diff --git a/src/random/randomInt.test.ts b/src/random/randomInt.test.ts index b8e45d2..b9c5efc 100644 --- a/src/random/randomInt.test.ts +++ b/src/random/randomInt.test.ts @@ -8,8 +8,8 @@ describe("randomInt", () => { }); test("args", async () => { - expect(randomInt(12, 20)).toBeGreaterThanOrEqual(12); - expect(randomInt(12, 12)).toBeLessThanOrEqual(12); - expect(randomInt(11, 12)).toBeLessThanOrEqual(13); + expect(randomInt({ min: 12, max: 20 })).toBeGreaterThanOrEqual(12); + expect(randomInt({ min: 12, max: 12 })).toBeLessThanOrEqual(12); + expect(randomInt({ min: 11, max: 12 })).toBeLessThanOrEqual(13); }); }); diff --git a/src/random/randomInt.ts b/src/random/randomInt.ts index f3717c2..706b958 100644 --- a/src/random/randomInt.ts +++ b/src/random/randomInt.ts @@ -1,35 +1,43 @@ -export const randomInt = (min: number = -100, max: number = 100): number => { +export const randomInt = ({ + min = -100, + max = 100, +}: { + min?: number; + max?: number; +} = {}): number => { min = Math.ceil(min); // in case is a float max = Math.floor(max); // in case is a float return Math.floor(Math.random() * (max - min + 1) + min); }; -export const randomPositiveInt = (max: number = 100): number => - randomInt(1, max); - -export const randomNegativeInt = (min: number = -100): number => - randomInt(min, -1); - -export const randomMaxSafeInt = () => - randomInt(-Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER); - -export const randomMaxInt = () => - randomInt(-Number.MAX_VALUE, Number.MAX_VALUE); - -export const randomPercentage = ({ - min, - max, +export const randomPositiveInt = ({ + min = 1, + max = 100, }: { min?: number; max?: number; -} = {}) => randomInt(min ?? -100, max ?? 100); +} = {}): number => + randomInt({ + min, + max, + }); -export const randomPositivePercentage = ({ - min, - max, +export const randomNegativeInt = ({ + min = -100, + max = -1, }: { min?: number; max?: number; -} = {}) => randomInt(min ?? 1, max ?? 100); +} = {}): number => + randomInt({ + min, + max, + }); + +export const randomMaxSafeInt = () => + randomInt({ min: -Number.MAX_SAFE_INTEGER, max: Number.MAX_SAFE_INTEGER }); + +export const randomMaxInt = () => + randomInt({ min: -Number.MAX_VALUE, max: Number.MAX_VALUE }); -export const randomFormattedPercentage = () => randomPercentage() + "%"; +export const randomFormattedPercentage = () => randomInt() + "%"; diff --git a/src/random/randomNumericCode.ts b/src/random/randomNumericCode.ts index f797466..5313e53 100644 --- a/src/random/randomNumericCode.ts +++ b/src/random/randomNumericCode.ts @@ -15,5 +15,7 @@ export const randomNumericCode = ({ length = 6 }: { length?: number } = {}) => { if (length < 1) throw new Error("randomNumericCode: Length must be greater than 0."); - return array(length, (_, index) => randomInt(!index ? 1 : 0, 9)).join(""); + return array(length, (_, index) => + randomInt({ min: !index ? 1 : 0, max: 9 }) + ).join(""); }; diff --git a/src/random/randomObject.ts b/src/random/randomObject.ts index 7f10d3b..b0c65b5 100644 --- a/src/random/randomObject.ts +++ b/src/random/randomObject.ts @@ -8,7 +8,7 @@ export const randomObject = ({ maxDepth = 5 }: { maxDepth?: number } = {}) => { const getRandomObject = (depth: number): PlainObject => { if (depth >= maxDepth) return {}; - const keys = array(randomInt(1, 5), randomNoun); + const keys = array(randomInt({ min: 1, max: 5 }), randomNoun); return keys.reduce((partial, key) => { partial[key] = randomValue() || getRandomObject(depth + 1); return partial; diff --git a/src/random/randomParagraph.ts b/src/random/randomParagraph.ts index f449aba..d145428 100644 --- a/src/random/randomParagraph.ts +++ b/src/random/randomParagraph.ts @@ -20,7 +20,7 @@ export const randomParagraph = ({ maxWords?: number; } = {}) => { return capitalize( - array(randomInt(minWords, maxWords), () => randomWord()) + array(randomInt({ min: minWords, max: maxWords }), () => randomWord()) .join(" ") .slice(0, maxCharacters - 1) + "." ); diff --git a/src/random/randomPassword.ts b/src/random/randomPassword.ts index 1af3c54..87cb25b 100644 --- a/src/random/randomPassword.ts +++ b/src/random/randomPassword.ts @@ -8,6 +8,6 @@ export const randomPassword = ({ maxChars = 32, }: { minChars?: number; maxChars?: number } = {}) => randomString({ length: 1 }).toUpperCase() + // Upper case - randomString({ length: randomInt(minChars, maxChars) - 3 }) + // At least 9 chars + randomString({ length: randomInt({ min: minChars, max: maxChars }) - 3 }) + // At least 9 chars randomArrayItem(SPECIAL_CHARACTERS) + // Special character - randomInt(1, 9); // Number + randomInt({ min: 1, max: 9 }); // Number