From 890bbe4458655f52edb16fa1bc853029d41af449 Mon Sep 17 00:00:00 2001 From: Phillip Hartin Date: Tue, 21 May 2024 17:39:50 +1000 Subject: [PATCH 01/41] Add test cases for generatePassword --- src/generators.test.ts | 18 +++++++++++------- src/generators.ts | 5 ++--- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/generators.test.ts b/src/generators.test.ts index 47810052..53854004 100644 --- a/src/generators.test.ts +++ b/src/generators.test.ts @@ -1,4 +1,4 @@ -import { expect, test } from 'vitest' +import { expect, test, describe } from 'vitest' import * as mod from './generators' test('generateNumber', () => { @@ -8,6 +8,7 @@ test('generateNumber', () => { expect(mod.generateNumber(4).toString()).toHaveLength(4) expect(mod.generateNumber(5).toString()).toHaveLength(5) expect(mod.generateNumber(10).toString()).toHaveLength(10) + expect(mod.generateNumber(0)).toBe(0) }) test('generateNumberBetween', () => { @@ -15,6 +16,8 @@ test('generateNumberBetween', () => { expect(mod.generateNumberBetween(5, 10)).toBeLessThanOrEqual(10) expect(mod.generateNumberBetween(10, 15)).toBeGreaterThanOrEqual(10) expect(mod.generateNumberBetween(10, 15)).toBeLessThanOrEqual(15) + expect(mod.generateNumberBetween(15, 10)).toBeLessThanOrEqual(15) + expect(mod.generateNumberBetween(1, 1)).toBe(1) }) test('generateUuid', () => { @@ -27,9 +30,10 @@ test('generateShortId', () => { expect(mod.generateShortId(19)).toMatch(/^[0-9a-zA-Z]{19}$/) }) -// test('generatePassword', () => { -// expect(mod.generatePassword(12)).toHaveLength(12) -// expect(mod.generatePassword(12)).toMatch(/^[0-9a-zA-Z]{12}$/) -// expect(mod.generatePassword(8)).toHaveLength(8) -// expect(mod.generatePassword(8)).toMatch(/^[0-9a-zA-Z]{8}$/) -// }) +test('generatePassword', () => { + expect(mod.generatePassword()).toHaveLength(8) + expect(mod.generatePassword({length: 12})).toHaveLength(12) + expect(mod.generatePassword({length: 8})).toHaveLength(8) + expect(mod.generatePassword({length: 12, special: 1})).toMatch(new RegExp(/^[0-9a-zA-Z!@#$%^&*()]{12}$/)) + expect(mod.generatePassword({length: 8, special: 1})).toMatch(new RegExp(/^[0-9a-zA-Z!@#$%^&*()]{8}$/)) +}) diff --git a/src/generators.ts b/src/generators.ts index 81e90e27..55306e2e 100644 --- a/src/generators.ts +++ b/src/generators.ts @@ -19,9 +19,8 @@ export function generateNumber(length: number): number { /** * Generate a random number between two values */ -export function generateNumberBetween(min: number, max: number): number { - if (min > max) console.warn('[MODS] Warning: min value is higher than max value') - return Math.floor(Math.random() * (max - min + 1) + min) +export function generateNumberBetween(from: number, to: number): number { + return Math.floor(Math.random() * (to - from + 1) + from) } /** From 5967f35f671ef74bad82b5594e0fe776efbd19e3 Mon Sep 17 00:00:00 2001 From: Jeremy Butler Date: Tue, 21 May 2024 20:17:31 +1000 Subject: [PATCH 02/41] chore: Update number calculation components to parse input as JSON --- nuxt-web/components/content/numbers/Average.vue | 2 +- nuxt-web/components/content/numbers/Max.vue | 4 ++-- nuxt-web/components/content/numbers/Mean.vue | 2 +- nuxt-web/components/content/numbers/Median.vue | 4 ++-- nuxt-web/components/content/numbers/Min.vue | 4 ++-- nuxt-web/components/content/numbers/MinMax.vue | 4 ++-- nuxt-web/components/content/numbers/Mode.vue | 4 ++-- nuxt-web/components/content/numbers/Range.vue | 2 +- nuxt-web/components/content/numbers/Skewness.vue | 2 +- nuxt-web/components/content/numbers/StandardDeviation.vue | 2 +- nuxt-web/components/content/numbers/Sum.vue | 2 +- 11 files changed, 16 insertions(+), 16 deletions(-) diff --git a/nuxt-web/components/content/numbers/Average.vue b/nuxt-web/components/content/numbers/Average.vue index 07cc841b..a33314c6 100644 --- a/nuxt-web/components/content/numbers/Average.vue +++ b/nuxt-web/components/content/numbers/Average.vue @@ -5,7 +5,7 @@ - {{ average(convertToArray(value)) }} + {{ average(JSON.parse(value)) }} diff --git a/nuxt-web/components/content/numbers/Max.vue b/nuxt-web/components/content/numbers/Max.vue index 9741b5a3..a6631881 100644 --- a/nuxt-web/components/content/numbers/Max.vue +++ b/nuxt-web/components/content/numbers/Max.vue @@ -3,9 +3,9 @@ - + - {{ max(convertToArray(value)) }} + {{ max(JSON.parse(value)) }} diff --git a/nuxt-web/components/content/numbers/Mean.vue b/nuxt-web/components/content/numbers/Mean.vue index 5ed3fdd2..77984363 100644 --- a/nuxt-web/components/content/numbers/Mean.vue +++ b/nuxt-web/components/content/numbers/Mean.vue @@ -5,7 +5,7 @@ - {{ mean(convertToArray(value)) }} + {{ mean(JSON.parse(value)) }} diff --git a/nuxt-web/components/content/numbers/Median.vue b/nuxt-web/components/content/numbers/Median.vue index f79bafd6..9c715ebb 100644 --- a/nuxt-web/components/content/numbers/Median.vue +++ b/nuxt-web/components/content/numbers/Median.vue @@ -3,9 +3,9 @@ - + - {{ median(convertToArray(value)) }} + {{ median(JSON.parse(value)) }} diff --git a/nuxt-web/components/content/numbers/Min.vue b/nuxt-web/components/content/numbers/Min.vue index 29944c10..3addb5b7 100644 --- a/nuxt-web/components/content/numbers/Min.vue +++ b/nuxt-web/components/content/numbers/Min.vue @@ -3,9 +3,9 @@ - + - {{ min(convertToArray(value)) }} + {{ min(JSON.parse(value)) }} diff --git a/nuxt-web/components/content/numbers/MinMax.vue b/nuxt-web/components/content/numbers/MinMax.vue index ae2ef70c..28362aca 100644 --- a/nuxt-web/components/content/numbers/MinMax.vue +++ b/nuxt-web/components/content/numbers/MinMax.vue @@ -3,9 +3,9 @@ - + - {{ minMax(convertToArray(value)) }} + {{ minMax(JSON.parse(value)) }} diff --git a/nuxt-web/components/content/numbers/Mode.vue b/nuxt-web/components/content/numbers/Mode.vue index 9e9372bb..ebe64ef5 100644 --- a/nuxt-web/components/content/numbers/Mode.vue +++ b/nuxt-web/components/content/numbers/Mode.vue @@ -3,9 +3,9 @@ - + - {{ mode(convertToArray(value)) }} + {{ mode(JSON.parse(value)) }} diff --git a/nuxt-web/components/content/numbers/Range.vue b/nuxt-web/components/content/numbers/Range.vue index d3763e09..e1fdd820 100644 --- a/nuxt-web/components/content/numbers/Range.vue +++ b/nuxt-web/components/content/numbers/Range.vue @@ -5,7 +5,7 @@ - {{ range(convertToArray(value)) }} + {{ range(JSON.parse(value)) }} diff --git a/nuxt-web/components/content/numbers/Skewness.vue b/nuxt-web/components/content/numbers/Skewness.vue index 8076a72b..f1f1051d 100644 --- a/nuxt-web/components/content/numbers/Skewness.vue +++ b/nuxt-web/components/content/numbers/Skewness.vue @@ -5,7 +5,7 @@ - {{ skewness(convertToArray(value)) }} + {{ skewness(JSON.parse(value)) }} diff --git a/nuxt-web/components/content/numbers/StandardDeviation.vue b/nuxt-web/components/content/numbers/StandardDeviation.vue index 5d3227cf..8e26768f 100644 --- a/nuxt-web/components/content/numbers/StandardDeviation.vue +++ b/nuxt-web/components/content/numbers/StandardDeviation.vue @@ -5,7 +5,7 @@ - {{ standardDeviation(convertToArray(value)) }} + {{ standardDeviation(JSON.parse(value)) }} diff --git a/nuxt-web/components/content/numbers/Sum.vue b/nuxt-web/components/content/numbers/Sum.vue index cb727fc4..c2f66e7f 100644 --- a/nuxt-web/components/content/numbers/Sum.vue +++ b/nuxt-web/components/content/numbers/Sum.vue @@ -5,7 +5,7 @@ - {{ sum(convertToArray(value)) }} + {{ sum(JSON.parse(value)) }} From df101dc26f4280ab16f95850875aa50ed915cff1 Mon Sep 17 00:00:00 2001 From: Jeremy Butler Date: Wed, 22 May 2024 06:46:28 +1000 Subject: [PATCH 03/41] Add tests for new functions --- src/numbers.test.ts | 101 +++++++++++++++++++++++++++++++++++++++++++ src/numbers.ts | 102 ++++++++++++++++++++++++++++++++++---------- 2 files changed, 180 insertions(+), 23 deletions(-) diff --git a/src/numbers.test.ts b/src/numbers.test.ts index 43e61fbf..4a994b92 100644 --- a/src/numbers.test.ts +++ b/src/numbers.test.ts @@ -2,9 +2,18 @@ import { expect, test } from 'vitest' import * as mod from './numbers' test('sum', () => { + expect(mod.sum([])).toBe(0) expect(mod.sum([1, 2, 3])).toBe(6) }) +test('mean', () => { + expect(mod.mean([1, 2, 3])).toBe(2) + expect(mod.mean([1, 2, 3, 4])).toBe(2.5) + expect(mod.mean([-5, -3, -1, 0, 2])).toBe(-1.4) + expect(mod.mean([42])).toBe(42) + expect(mod.mean([])).toBe(0) +}) + test('average', () => { expect(mod.average([1, 2, 3])).toBe(2) expect(mod.average([1, 2, 3, 4])).toBe(2.5) @@ -13,6 +22,48 @@ test('average', () => { expect(mod.average([])).toBe(0) }) +test('margin', () => { + expect(mod.margin(100, 10)).toBe(10) + expect(mod.margin(100, 50)).toBe(50) + expect(mod.margin(100, 0)).toBe(0) +}) + +test('addMargin', () => { + expect(mod.addMargin(100, 10)).toBe(110) + expect(mod.addMargin(100, 50)).toBe(150) + expect(mod.addMargin(100, 0)).toBe(100) +}) + +test('subtractMargin', () => { + expect(mod.subtractMargin(0, 10)).toBe(0) + expect(mod.subtractMargin(100, 10)).toBe(90) + expect(mod.subtractMargin(100, 50)).toBe(50) + expect(mod.subtractMargin(100, 0)).toBe(100) +}) + +test('addMarkup', () => { + expect(mod.addMarkup(0, 10)).toBe(0) + expect(mod.addMarkup(100, 10)).toBe(110) + expect(mod.addMarkup(100, 50)).toBe(150) + expect(mod.addMarkup(100, 0)).toBe(100) +}) + +test('subtractMarkup', () => { + expect(mod.subtractMarkup(0, 10)).toBe(0) + expect(mod.subtractMarkup(110, 10)).toBe(100) + expect(mod.subtractMarkup(150, 50)).toBe(100) + expect(mod.subtractMarkup(100, 0)).toBe(100) +}) + +test('mode', () => { + expect(mod.mode([1])).toEqual([1]) + expect(mod.mode([1, 2, 3, 3])).toEqual([3]) + expect(mod.mode([1, 2, 3, 3, 2])).toEqual([2, 3]) + expect(mod.mode([1, 2, 3, 3, 2, 1])).toEqual([1, 2, 3]) + expect(mod.mode([1, 2, 3, 4])).toEqual(null) + expect(mod.mode([])).toEqual(null) +}) + test('median', () => { expect(mod.median([1, 2, 3])).toBe(2) expect(mod.median([1, 2, 3, 4])).toBe(2.5) @@ -20,3 +71,53 @@ test('median', () => { expect(mod.median([42])).toBe(42) expect(mod.median([])).toBeNaN() }) + +test('min', () => { + expect(mod.min([1, 2, 3])).toBe(1) + expect(mod.min([1, 2, 3, 4])).toBe(1) + expect(mod.min([-5, -3, -1, 0, 2])).toBe(-5) + expect(mod.min([42])).toBe(42) + expect(mod.min([])).toBe(0) +}) + +test('max', () => { + expect(mod.max([1, 2, 3])).toBe(3) + expect(mod.max([1, 2, 3, 4])).toBe(4) + expect(mod.max([-5, -3, -1, 0, 2])).toBe(2) + expect(mod.max([42])).toBe(42) + expect(mod.max([])).toBe(0) +}) + +test('minMax', () => { + expect(mod.minMax([1, 2, 3])).toEqual([1, 3]) + expect(mod.minMax([1, 2, 3, 4])).toEqual([1, 4]) + expect(mod.minMax([-5, -3, -1, 0, 2])).toEqual([-5, 2]) + expect(mod.minMax([42])).toEqual([42, 42]) + expect(mod.minMax([])).toEqual([0, 0]) +}) + +test('range', () => { + expect(mod.range([1, 2, 3])).toBe(2) + expect(mod.range([1, 2, 3, 4])).toBe(3) + expect(mod.range([-5, -3, -1, 0, 2])).toBe(7) + expect(mod.range([42])).toBe(0) + expect(mod.range([])).toBeNaN() +}) + +test('standardDeviation', () => { + expect(mod.standardDeviation([1, 2, 3])).toBeCloseTo(0.82, 2) + expect(mod.standardDeviation([1, 2, 3, 4])).toBeCloseTo(1.12, 2) + expect(mod.standardDeviation([1, 2, 3, 4], {method: 'sample'})).toBeCloseTo(1.2909944487358056, 2) + expect(mod.standardDeviation([-5, -3, -1, 0, 2])).toBeCloseTo(2.4166091947189146, 2) + expect(mod.standardDeviation([42])).toBe(0) + expect(mod.standardDeviation([])).toBeNaN() +}) + +test('skewness', () => { + expect(mod.skewness([1, 2, 3])).toBe(0) + expect(mod.skewness([1, 2, 3, 4])).toBe(0) + expect(mod.skewness([5, 5, 5, 5])).toBe(0) + expect(mod.skewness([-5, -3, -1, 0, 2])).toBeCloseTo(-0.255084, 2) + expect(mod.skewness([42])).toBeNaN() + expect(mod.skewness([])).toBeNaN() +}) \ No newline at end of file diff --git a/src/numbers.ts b/src/numbers.ts index d60217ef..440f17bb 100644 --- a/src/numbers.ts +++ b/src/numbers.ts @@ -14,8 +14,12 @@ export function sum(numbers: number[]): number { * Calculates the mean of an array of numbers. */ export function mean(numbers: number[]): number { - if (numbers.length === 0) return 0 - return sum(numbers) / numbers.length + if (numbers.length === 0) { + console.log("[MODS] mean array is empty.") + return 0 + } + const sum = numbers.reduce((acc, val) => acc + val, 0) + return sum / numbers.length } /** @@ -48,17 +52,25 @@ export function subtractMargin(value: number, percentage: number): number { } /** - * Calculates the markup based on a percentage. + * Adds the markup to the value. */ -export function subtractMarkup(value: number, percentage: number): number { - return value / (1 + percentage / 100) +export function addMarkup(value: number, percentage: number): number { + if (value === 0) { + console.log("[MODS] addMarkup value is 0.") + return 0 + } + return Math.round(value * (1 + percentage / 100) * 100) / 100 } /** - * Adds the markup to the value. + * Calculates the markup based on a percentage. */ -export function addMarkup(value: number, percentage: number): number { - return value + subtractMarkup(value, percentage) +export function subtractMarkup(value: number, percentage: number): number { + if (value === 0) { + console.log("[MODS] subtractMarkup value is 0.") + return 0 + } + return Math.round((value / (1 + percentage / 100)) * 100) / 100 } /** @@ -78,20 +90,38 @@ export function median(numbers: number[]): number { /** * Calculates the mode of an array of numbers. */ -export function mode(numbers: number[]): number | null { +export function mode(numbers: number[]): number[] | null { if (numbers.length === 0) return null - if (numbers.length === 1) return numbers[0] + if (numbers.length === 1) return [numbers[0]] + const frequencyMap = new Map() - numbers.forEach((num) => frequencyMap.set(num, (frequencyMap.get(num) || 0) + 1)) - const maxEntry = [...frequencyMap.entries()].reduce((a, b) => (a[1] > b[1] ? a : b)) - if (maxEntry[1] > 1) return maxEntry[0] - return null + let maxFrequency = 0 + + numbers.forEach((num) => { + const frequency = (frequencyMap.get(num) || 0) + 1 + frequencyMap.set(num, frequency) + if (frequency > maxFrequency) { + maxFrequency = frequency + } + }) + + if (maxFrequency === 1) return null + + const modes = [...frequencyMap.entries()] + .filter(([_, freq]) => freq === maxFrequency) + .map(([num, _]) => num) + + return modes } /** * Finds the minimum value in an array of numbers. */ export function min(numbers: number[]): number { + if (numbers.length === 0) { + console.log("[MODS] min array is empty.") + return 0 + } return Math.min(...numbers) } @@ -99,6 +129,10 @@ export function min(numbers: number[]): number { * Finds the maximum value in an array of numbers. */ export function max(numbers: number[]): number { + if (numbers.length === 0) { + console.log("[MODS] max array is empty.") + return 0 + } return Math.max(...numbers) } @@ -106,7 +140,10 @@ export function max(numbers: number[]): number { * Returns the minimum and maximum values in an array of numbers. */ export function minMax(numbers: number[]): [number, number] { - if (numbers.length === 0) return [0, 0] + if (numbers.length === 0) { + console.log("[MODS] minMax array is empty.") + return [0, 0] + } return [min(numbers), max(numbers)] } @@ -114,24 +151,43 @@ export function minMax(numbers: number[]): [number, number] { * Returns the difference between two values, expressed as a positive number. */ export function range(numbers: number[]): number { + if (numbers.length === 0) { + console.log("[MODS] range array is empty.") + return NaN + } return max(numbers) - min(numbers) } /** * Returns the standard deviation of an array of numbers. */ -export function standardDeviation(numbers: number[]): number { - return Math.sqrt(mean(numbers.map((num) => Math.pow(num - mean(numbers), 2)))) +export function standardDeviation(numbers: number[], options?: { method: 'sample' | 'population' }): number { + if (numbers.length === 0) { + console.log("[MODS] standardDeviation array is empty.") + return NaN + } + options = options || { method: 'population' } + const meanValue = mean(numbers) + const n = options.method === 'sample' ? numbers.length - 1 : numbers.length + const sum = numbers.reduce((acc, num) => acc + (num - meanValue) ** 2, 0) + return Math.sqrt(sum / n) } + /** * Returns the measure of asymmetry of the probability distribution of an array of numbers. The skewness value can be positive, zero, negative, or undefined. */ export function skewness(numbers: number[]): number { - const n = numbers.length - const meanValue = mean(numbers) - if (standardDeviation(numbers) === 0) return 0 - let sum = 0 - for (const num of numbers) sum += (num - meanValue) ** 3 - return (n / ((n - 1) * (n - 2))) * (sum / standardDeviation(numbers) ** 3) + const n = numbers.length; + if (n < 3) { + console.log("[MODS] skewness requires at least 3 numbers."); + return NaN; + } + + const meanValue = mean(numbers); + const stdDev = standardDeviation(numbers); + if (stdDev === 0) return 0; + + const sumCubedDeviations = numbers.reduce((acc, num) => acc + (num - meanValue) ** 3, 0); + return (n / ((n - 1) * (n - 2))) * (sumCubedDeviations / (stdDev ** 3)); } From f131afe611f1fe91a8eafc228c5bcacd5cd260ba Mon Sep 17 00:00:00 2001 From: Jeremy Butler Date: Wed, 22 May 2024 06:46:41 +1000 Subject: [PATCH 04/41] Remove unused function convertToArray() and update currencySymbols map --- src/config.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/config.ts b/src/config.ts index f4d5e02b..69e492ad 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,11 +1,3 @@ -export function convertToArray(value: string) { - try { - return JSON.parse(value) - } catch (error) { - return [] - } -} - export const currencySymbols = new Map([ ['en-US', 'USD'], // US Dollar ['en-GB', 'GBP'], // British Pound From 002deafdc444acb6bf4234c6ff615b432d2ce780 Mon Sep 17 00:00:00 2001 From: Jeremy Butler Date: Wed, 22 May 2024 06:46:49 +1000 Subject: [PATCH 05/41] Refactor data.test.ts: Add tests for dataReverse and dataSortBy functions --- src/data.test.ts | 21 +- src/validators.test.ts | 454 ++++++++++++++++++++++------------------- 2 files changed, 256 insertions(+), 219 deletions(-) diff --git a/src/data.test.ts b/src/data.test.ts index 50c2a844..82a60c21 100644 --- a/src/data.test.ts +++ b/src/data.test.ts @@ -1,22 +1,33 @@ import { expect, test } from 'vitest' import * as mod from './data' -const items = [ +const arrayExample = [ { name: 'John', age: 25 }, { name: 'Jane', age: 30 }, { name: 'Jake', age: 20 } ] +const objectExample = { + name: 'John', + age: 25, + country: 'USA' +} + // test('dataShuffle', () => { // expect(mod.dataShuffle(items)).not.toStrictEqual(items) // }) test('dataReverse', () => { - expect(mod.dataReverse(items)).toStrictEqual([ + expect(mod.dataReverse(arrayExample)).toStrictEqual([ { name: 'Jake', age: 20 }, { name: 'Jane', age: 30 }, { name: 'John', age: 25 } ]) + expect(mod.dataReverse(objectExample)).toStrictEqual({ + country: 'USA', + age: 25, + name: 'John', + }) }) test('dataRemoveDuplicates', () => { @@ -25,19 +36,19 @@ test('dataRemoveDuplicates', () => { }) test('dataSortBy', () => { - expect(mod.dataSortBy(items, { property: 'age' })).toStrictEqual([ + expect(mod.dataSortBy(arrayExample, { property: 'age' })).toStrictEqual([ { name: 'Jake', age: 20 }, { name: 'John', age: 25 }, { name: 'Jane', age: 30 } ]) - expect(mod.dataSortBy(items, { property: 'age', order: 'desc' })).toStrictEqual([ + expect(mod.dataSortBy(arrayExample, { property: 'age', order: 'desc' })).toStrictEqual([ { name: 'Jane', age: 30 }, { name: 'John', age: 25 }, { name: 'Jake', age: 20 } ]) - expect(mod.dataSortBy(items, { property: 'name' })).toStrictEqual([ + expect(mod.dataSortBy(arrayExample, { property: 'name' })).toStrictEqual([ { name: 'Jake', age: 20 }, { name: 'Jane', age: 30 }, { name: 'John', age: 25 } diff --git a/src/validators.test.ts b/src/validators.test.ts index cc4708a6..307965ff 100644 --- a/src/validators.test.ts +++ b/src/validators.test.ts @@ -1,217 +1,243 @@ -import { expect, test } from 'vitest' -import * as mod from './validators' +import { expect, test } from "vitest"; +import * as mod from "./validators"; -test('isEmail', () => { +test("isEmail", () => { // Valid email addresses - expect(mod.isEmail('hello@email.com')).toBe(true) - expect(mod.isEmail('john.doe+marketing@email.hospital')).toBe(true) - expect(mod.isEmail('jane_d@example.abogado')).toBe(true) - expect(mod.isEmail('first.last@sub.domain.com')).toBe(true) - expect(mod.isEmail('_Yosemite.Sam@example.com')).toBe(true) - expect(mod.isEmail('firstname-lastname@example.com')).toBe(true) - expect(mod.isEmail('email@example.co.uk')).toBe(true) - expect(mod.isEmail('1234567890@example.com')).toBe(true) - expect(mod.isEmail('email@example.name')).toBe(true) - expect(mod.isEmail('email@example.museum')).toBe(true) - expect(mod.isEmail('email@example.travel')).toBe(true) - + expect(mod.isEmail("hello@email.com")).toBe(true); + expect(mod.isEmail("john.doe+marketing@email.hospital")).toBe(true); + expect(mod.isEmail("jane_d@example.abogado")).toBe(true); + expect(mod.isEmail("first.last@sub.domain.com")).toBe(true); + expect(mod.isEmail("_Yosemite.Sam@example.com")).toBe(true); + expect(mod.isEmail("firstname-lastname@example.com")).toBe(true); + expect(mod.isEmail("email@example.co.uk")).toBe(true); + expect(mod.isEmail("1234567890@example.com")).toBe(true); + expect(mod.isEmail("email@example.name")).toBe(true); + expect(mod.isEmail("email@example.museum")).toBe(true); + expect(mod.isEmail("email@example.travel")).toBe(true); // Invalid email addresses - expect(mod.isEmail('helloemail.com')).toBe(false) // Missing @ - expect(mod.isEmail('plainaddress')).toBe(false) // Missing @ and domain - expect(mod.isEmail('@missing-local-part.com')).toBe(false) // Missing local part - expect(mod.isEmail('missing-at-sign.com')).toBe(false) // Missing @ - expect(mod.isEmail('missing-domain@.com')).toBe(false) // Missing domain - expect(mod.isEmail('missing-tld@domain.')).toBe(false) // Missing TLD - expect(mod.isEmail('missingdot@com')).toBe(false) // Missing dot in domain - expect(mod.isEmail('two..dots@example.com')).toBe(false) // Consecutive dots in local part - expect(mod.isEmail('john.doe@example..com')).toBe(false) // Consecutive dots in domain - expect(mod.isEmail('jane_d@example.abogado@com')).toBe(false) // Two @ signs - expect(mod.isEmail('hello@123.123.123.123')).toBe(false) // IP address instead of domain - expect(mod.isEmail('john.doe.@example.com')).toBe(false) // Trailing dot in local part - expect(mod.isEmail('john..doe@example.com')).toBe(false) // Consecutive dots in local part - expect(mod.isEmail('john.doe@example')).toBe(false) // Missing TLD - expect(mod.isEmail('jd+m@email.c')).toBe(false) // TLD too short - expect(mod.isEmail('h#email.com')).toBe(false) // Invalid character in local part -}) - -test('isNumber', () => { - expect(mod.isNumber(0)).toBe(true) - expect(mod.isNumber(123)).toBe(true) - expect(mod.isNumber('123')).toBe(false) - expect(mod.isNumber('abc')).toBe(false) -}) - -test('isUrl', () => { - expect(mod.isUrl('https://usemods.com')).toBe(true) - expect(mod.isUrl('https://www.usemods.com')).toBe(true) - expect(mod.isUrl('ftp://192.168.0.1')).toBe(true) - expect(mod.isUrl('www.usemods')).toBe(false) - expect(mod.isUrl('usemods.com')).toBe(false) - expect(mod.isUrl('com.usemods')).toBe(false) - expect(mod.isUrl('usemods')).toBe(false) -}) - -test('isUuid', () => { - expect(mod.isUuid('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11')).toBe(true) - expect(mod.isUuid('hello')).toBe(false) -}) - -test('isJson', () => { - expect(mod.isJson('{"hello": "world"}')).toBe(true) - expect(mod.isJson('{"hello": world}')).toBe(false) - expect(mod.isJson('hello')).toBe(false) -}) - -test('isHex', () => { - expect(mod.isHex('#ffffff')).toBe(true) - expect(mod.isHex('#gggggg')).toBe(false) -}) - -test('isEmpty', () => { - expect(mod.isEmpty('')).toBe(true) - expect(mod.isEmpty([])).toBe(true) - expect(mod.isEmpty({})).toBe(true) - expect(mod.isEmpty(null)).toBe(true) - expect(mod.isEmpty(undefined)).toBe(true) - expect(mod.isEmpty('hello')).toBe(false) - expect(mod.isEmpty([1])).toBe(false) - expect(mod.isEmpty({ key: 'value' })).toBe(false) -}) - -test('isAlphabetic', () => { - expect(mod.isAlphabetic('hello')).toBe(true) - expect(mod.isAlphabetic('hello123')).toBe(false) - expect(mod.isAlphabetic(123)).toBe(false) - expect(mod.isAlphabetic(['a', 'b', 'c'])).toBe(false) -}) - -test('isAlphanumeric', () => { - expect(mod.isAlphanumeric('hello')).toBe(true) - expect(mod.isAlphanumeric(123)).toBe(true) - expect(mod.isAlphanumeric('hello123')).toBe(true) - expect(mod.isAlphanumeric('hello!')).toBe(false) - expect(mod.isAlphanumeric(['a', 2, '!'])).toBe(false) -}) - -test('isArray', () => { - expect(mod.isArray([])).toBe(true) - expect(mod.isArray(['hello'])).toBe(true) - expect(mod.isArray('hello')).toBe(false) -}) - -test('isObject', () => { - expect(mod.isObject({ hello: 'world' })).toBe(true) - expect(mod.isObject('hello')).toBe(false) -}) - -test('isBoolean', () => { - expect(mod.isBoolean(true)).toBe(true) - expect(mod.isBoolean(1)).toBe(false) - expect(mod.isBoolean('hello')).toBe(false) -}) - -test('isDate', () => { - expect(mod.isDate(new Date())).toBe(true) - expect(mod.isDate('2024-01-01')).toBe(true) - expect(mod.isDate('2024-01-day')).toBe(false) - expect(mod.isDate('hello')).toBe(false) -}) - -test('isUndefined', () => { - expect(mod.isUndefined(undefined)).toBe(true) - expect(mod.isUndefined('hello')).toBe(false) -}) - -test('isNull', () => { - expect(mod.isNull(null)).toBe(true) - expect(mod.isNull('hello')).toBe(false) -}) - -test('isTime', () => { - expect(mod.isTime('12:00')).toBe(true) - expect(mod.isTime('hello')).toBe(false) -}) - -test('isLeapYear', () => { - expect(mod.isLeapYear(2020)).toBe(true) - expect(mod.isLeapYear(2021)).toBe(false) -}) - -test('isEven', () => { - expect(mod.isEven(2)).toBe(true) - expect(mod.isEven(3)).toBe(false) -}) - -test('isOdd', () => { - expect(mod.isOdd(2)).toBe(false) - expect(mod.isOdd(3)).toBe(true) -}) - -test('isPositive', () => { - expect(mod.isPositive(2)).toBe(true) - expect(mod.isPositive(-2)).toBe(false) -}) - -test('isNegative', () => { - expect(mod.isNegative(2)).toBe(false) - expect(mod.isNegative(-2)).toBe(true) -}) - -test('isPrime', () => { - expect(mod.isPrime(2)).toBe(true) - expect(mod.isPrime(4)).toBe(false) -}) - -test('isInteger', () => { - expect(mod.isInteger(2)).toBe(true) - expect(mod.isInteger(2.5)).toBe(false) -}) - -test('isFloat', () => { - expect(mod.isFloat(2.5)).toBe(true) - expect(mod.isFloat(2)).toBe(false) -}) - -test('isBetween', () => { - expect(mod.isBetween(4, 2, 6)).toBe(true) - expect(mod.isBetween(4, 6, 8)).toBe(false) -}) - -test('isDivisibleBy', () => { - expect(mod.isDivisibleBy(4, 2)).toBe(true) - expect(mod.isDivisibleBy(4, 3)).toBe(false) -}) - -test('isCreditCard', () => { - expect(mod.isCreditCard('4111111111111111')).toBe(true) - expect(mod.isCreditCard('hello')).toBe(false) -}) - -test('isIpAddress', () => { - expect(mod.isIpAddress('192.168.0.1')).toBe(true) - expect(mod.isIpAddress('192.168.0.1:3000')).toBe(true) - expect(mod.isIpAddress('00:00:00:00:00:00')).toBe(false) - expect(mod.isIpAddress('hello')).toBe(false) -}) - -test('isMacAddress', () => { - expect(mod.isMacAddress('00:00:00:00:00:00')).toBe(true) - expect(mod.isMacAddress('hello')).toBe(false) -}) - -test('isLatLng', () => { - expect(mod.isLatLng('12.345678,-98.765432')).toBe(true) - expect(mod.isLatLng('12.345678, -98.765432')).toBe(true) - expect(mod.isLatLng('98.765432,12.345678')).toBe(false) - expect(mod.isLatLng('hello')).toBe(false) -}) - -test('isLatitude', () => { - expect(mod.isLatitude('12.345678')).toBe(true) - expect(mod.isLatitude('hello')).toBe(false) -}) - -test('isLongitude', () => { - expect(mod.isLongitude('-98.765432')).toBe(true) - expect(mod.isLongitude('hello')).toBe(false) -}) + expect(mod.isEmail("helloemail.com")).toBe(false); // Missing @ + expect(mod.isEmail("plainaddress")).toBe(false); // Missing @ and domain + expect(mod.isEmail("@missing-local-part.com")).toBe(false); // Missing local part + expect(mod.isEmail("missing-at-sign.com")).toBe(false); // Missing @ + expect(mod.isEmail("missing-domain@.com")).toBe(false); // Missing domain + expect(mod.isEmail("missing-tld@domain.")).toBe(false); // Missing TLD + expect(mod.isEmail("missingdot@com")).toBe(false); // Missing dot in domain + expect(mod.isEmail("two..dots@example.com")).toBe(false); // Consecutive dots in local part + expect(mod.isEmail("john.doe@example..com")).toBe(false); // Consecutive dots in domain + expect(mod.isEmail("jane_d@example.abogado@com")).toBe(false); // Two @ signs + expect(mod.isEmail("hello@123.123.123.123")).toBe(false); // IP address instead of domain + expect(mod.isEmail("john.doe.@example.com")).toBe(false); // Trailing dot in local part + expect(mod.isEmail("john..doe@example.com")).toBe(false); // Consecutive dots in local part + expect(mod.isEmail("john.doe@example")).toBe(false); // Missing TLD + expect(mod.isEmail("jd+m@email.c")).toBe(false); // TLD too short + expect(mod.isEmail("h#email.com")).toBe(false); // Invalid character in local part +}); + +test("isNumber", () => { + expect(mod.isNumber(0)).toBe(true); + expect(mod.isNumber(123)).toBe(true); + expect(mod.isNumber("123")).toBe(false); + expect(mod.isNumber("abc")).toBe(false); +}); + +test("isUrl", () => { + expect(mod.isUrl("https://usemods.com")).toBe(true); + expect(mod.isUrl("https://www.usemods.com")).toBe(true); + expect(mod.isUrl("ftp://192.168.0.1")).toBe(true); + expect(mod.isUrl("www.usemods")).toBe(false); + expect(mod.isUrl("usemods.com")).toBe(false); + expect(mod.isUrl("com.usemods")).toBe(false); + expect(mod.isUrl("usemods")).toBe(false); +}); + +test("isUuid", () => { + expect(mod.isUuid("a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11")).toBe(true); + expect(mod.isUuid("hello")).toBe(false); +}); + +test("isJson", () => { + expect(mod.isJson('{"hello": "world"}')).toBe(true); + expect(mod.isJson('{"hello": world}')).toBe(false); + expect(mod.isJson("hello")).toBe(false); +}); + +test("isHex", () => { + expect(mod.isHex("#ffffff")).toBe(true); + expect(mod.isHex("#gggggg")).toBe(false); +}); + +test("isEmpty", () => { + expect(mod.isEmpty("")).toBe(true); + expect(mod.isEmpty([])).toBe(true); + expect(mod.isEmpty({})).toBe(true); + expect(mod.isEmpty(null)).toBe(true); + expect(mod.isEmpty(undefined)).toBe(true); + expect(mod.isEmpty("hello")).toBe(false); + expect(mod.isEmpty([1])).toBe(false); + expect(mod.isEmpty({ key: "value" })).toBe(false); +}); + +test("isAlphabetic", () => { + expect(mod.isAlphabetic("hello")).toBe(true); + expect(mod.isAlphabetic("hello123")).toBe(false); + expect(mod.isAlphabetic(123)).toBe(false); + expect(mod.isAlphabetic(["a", "b", "c"])).toBe(false); +}); + +test("isAlphanumeric", () => { + expect(mod.isAlphanumeric("hello")).toBe(true); + expect(mod.isAlphanumeric(123)).toBe(true); + expect(mod.isAlphanumeric("hello123")).toBe(true); + expect(mod.isAlphanumeric("hello!")).toBe(false); + expect(mod.isAlphanumeric(["a", 2, "!"])).toBe(false); +}); + +test("isArray", () => { + expect(mod.isArray([])).toBe(true); + expect(mod.isArray(["hello"])).toBe(true); + expect(mod.isArray("hello")).toBe(false); +}); + +test("isObject", () => { + expect(mod.isObject({ hello: "world" })).toBe(true); + expect(mod.isObject("hello")).toBe(false); +}); + +test("isBoolean", () => { + expect(mod.isBoolean(true)).toBe(true); + expect(mod.isBoolean(1)).toBe(false); + expect(mod.isBoolean("hello")).toBe(false); +}); + +test("isDate", () => { + expect(mod.isDate(new Date())).toBe(true); + expect(mod.isDate("2024-01-01")).toBe(true); + expect(mod.isDate("2024-01-day")).toBe(false); + expect(mod.isDate("hello")).toBe(false); + // @ts-expect-error - Testing invalid date object + expect(mod.isDate({ year: 2024, month: 1, day: 1 })).toBe(false); +}); + +test("isPort", () => { + expect(mod.isPort(80)).toBe(true); + expect(mod.isPort(65535)).toBe(true); + expect(mod.isPort(65536)).toBe(false); + expect(mod.isPort(-1)).toBe(false); + expect(mod.isPort("hello")).toBe(false); +}) + +test("isUndefined", () => { + expect(mod.isUndefined(undefined)).toBe(true); + expect(mod.isUndefined("hello")).toBe(false); +}); + +test("isNull", () => { + expect(mod.isNull(null)).toBe(true); + expect(mod.isNull("hello")).toBe(false); +}); + +test("isTime", () => { + expect(mod.isTime("12:00")).toBe(true); + expect(mod.isTime("hello")).toBe(false); +}); + +test("isLeapYear", () => { + expect(mod.isLeapYear(2020)).toBe(true); + expect(mod.isLeapYear(2021)).toBe(false); +}); + +test("isEven", () => { + expect(mod.isEven(2)).toBe(true); + expect(mod.isEven(3)).toBe(false); +}); + +test("isOdd", () => { + expect(mod.isOdd(2)).toBe(false); + expect(mod.isOdd(3)).toBe(true); +}); + +test("isPositive", () => { + expect(mod.isPositive(2)).toBe(true); + expect(mod.isPositive(-2)).toBe(false); +}); + +test("isNegative", () => { + expect(mod.isNegative(2)).toBe(false); + expect(mod.isNegative(-2)).toBe(true); +}); + +test("isPrime", () => { + expect(mod.isPrime(2)).toBe(true); + expect(mod.isPrime(4)).toBe(false); +}); + +test("isInteger", () => { + expect(mod.isInteger(2)).toBe(true); + expect(mod.isInteger(2.5)).toBe(false); + expect(mod.isInteger("hello")).toBe(false); + expect(mod.isInteger("2")).toBe(false); + expect(mod.isInteger({})).toBe(false); +}); + +test("isFloat", () => { + expect(mod.isFloat(2.5)).toBe(true); + expect(mod.isFloat({})).toBe(false); + expect(mod.isFloat(2)).toBe(false); +}); + +test("isBetween", () => { + expect(mod.isBetween(4, 2, 6)).toBe(true); + expect(mod.isBetween(4, 6, 8)).toBe(false); +}); + +test("isDivisibleBy", () => { + expect(mod.isDivisibleBy(4, 2)).toBe(true); + expect(mod.isDivisibleBy(4, 3)).toBe(false); +}); + +test("isOver9000", () => { + expect(mod.isOver9000(9001)).toBe(true); + expect(mod.isOver9000(9000)).toBe(false); +}); + +test("isCreditCard", () => { + expect(mod.isCreditCard("4111111111111111")).toBe(true); + expect(mod.isCreditCard(4111111111111111)).toBe(true); + expect(mod.isCreditCard("hello")).toBe(false); + expect(mod.isCreditCard({})).toBe(false); + expect(mod.isCreditCard(1234567890123456)).toBe(false); +}); + +test("isZero", () => { + expect(mod.isZero(0)).toBe(true); + expect(mod.isZero(1)).toBe(false); +}); + +test("isIpAddress", () => { + expect(mod.isIpAddress("192.168.0.1")).toBe(true); + expect(mod.isIpAddress("192.168.0.1:3000")).toBe(true); + expect(mod.isIpAddress("00:00:00:00:00:00")).toBe(false); + expect(mod.isIpAddress("hello")).toBe(false); +}); + +test("isMacAddress", () => { + expect(mod.isMacAddress("00:00:00:00:00:00")).toBe(true); + expect(mod.isMacAddress("hello")).toBe(false); +}); + +test("isLatLng", () => { + expect(mod.isLatLng("12.345678,-98.765432")).toBe(true); + expect(mod.isLatLng("12.345678, -98.765432")).toBe(true); + expect(mod.isLatLng("98.765432,12.345678")).toBe(false); + expect(mod.isLatLng("hello")).toBe(false); +}); + +test("isLatitude", () => { + expect(mod.isLatitude("12.345678")).toBe(true); + expect(mod.isLatitude("hello")).toBe(false); +}); + +test("isLongitude", () => { + expect(mod.isLongitude("-98.765432")).toBe(true); + expect(mod.isLongitude("hello")).toBe(false); +}); From 861c1e57c6786e27b033224cc2436e9071f46bd8 Mon Sep 17 00:00:00 2001 From: Jeremy Butler Date: Wed, 22 May 2024 06:46:53 +1000 Subject: [PATCH 06/41] Update dataReverse function to reverse an array or object --- src/data.ts | 2 +- src/validators.ts | 191 +++++++++++++++++++++++++--------------------- 2 files changed, 106 insertions(+), 87 deletions(-) diff --git a/src/data.ts b/src/data.ts index 19d44174..a481465b 100644 --- a/src/data.ts +++ b/src/data.ts @@ -27,7 +27,7 @@ export function dataSortBy(items: object | string[] | number[], options?: { prop } /** - * Reverse an array. + * Reverse an array or object. */ export function dataReverse(items: object | string[] | number[]): object | string[] | number[] { if (!items) { diff --git a/src/validators.ts b/src/validators.ts index 39cd84ff..242cac42 100644 --- a/src/validators.ts +++ b/src/validators.ts @@ -6,45 +6,51 @@ * Check if any given value is a valid email address. */ export function isEmail(value: string): boolean { - const regex = /^(?!.*[._+-]{2})(?!.*[._+-]$)[a-zA-Z0-9._+-]+(? 9000 + return value > 9000; } /** * Check if the number is a prime number. */ export function isPrime(value: number): boolean { - const boundary = Math.floor(Math.sqrt(value)) + const boundary = Math.floor(Math.sqrt(value)); for (let i = 2; i <= boundary; i++) { - if (value % i === 0) return false + if (value % i === 0) return false; } - return value >= 2 + return value >= 2; } /** * Check if the number is an integer. */ -export function isInteger(value: number): boolean { - if (!isNumber(value)) return false - return value % 1 === 0 +export function isInteger(value: any): boolean { + if (typeof value !== "number") return false; + if (!isNumber(value)) return false; + return (value) % 1 === 0; } /** * Check if the number is a float. */ -export function isFloat(value: number): boolean { - if (!isNumber(value)) return false - return !isInteger(value) +export function isFloat(value: any): boolean { + if (typeof value !== "number") return false; + if (!isNumber(value)) return false; + return !isInteger(value); } /** @@ -220,174 +232,181 @@ export function isFloat(value: number): boolean { */ export function isBetween(value: number, min: number, max: number): boolean { if (min > max) { - ;[min, max] = [max, min] + [min, max] = [max, min]; } - return value >= min && value <= max + return value >= min && value <= max; } /** * Check if the number is divisible by the specified number. */ export function isDivisibleBy(value: number, divisor: number): boolean { - return value % divisor === 0 + return value % divisor === 0; } /** * Check if any given value is a valid credit card number. */ -export function isCreditCard(value: string): boolean { - const regex = /^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11})$/ - return regex.test(value) +export function isCreditCard(value: any): boolean { + if (typeof value === "number") value = value.toString(); + if (typeof value !== "string") return false; + const regex = + /^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:0111|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11})$/; + return regex.test(value); } /** * Check if any given value is a valid latitude-longitude coordinate in the format lat,lng or lat,lng. */ export function isLatLng(value: string): boolean { - const regex = /^([-+]?([1-8]?\d(\.\d+)?|90(\.0+)?)),\s*([-+]?(180(\.0+)?|((1[0-7]\d)|([1-9]?\d))(\.\d+)?))$/ - return regex.test(value) + const regex = + /^([-+]?([1-8]?\d(\.\d+)?|90(\.0+)?)),\s*([-+]?(180(\.0+)?|((1[0-7]\d)|([1-9]?\d))(\.\d+)?))$/; + return regex.test(value); } /** * Check if any given value is a valid latitude coordinate. */ export function isLatitude(value: string): boolean { - const regex = /^[-+]?([1-8]?\d(\.\d{1,6})?|90(\.0{1,6})?)$/ - return regex.test(value) + const regex = /^[-+]?([1-8]?\d(\.\d{1,6})?|90(\.0{1,6})?)$/; + return regex.test(value); } /** * Check if any given value is a valid longitude coordinate. */ export function isLongitude(value: string): boolean { - const regex = /^[-+]?(180(\.0{1,6})?|((1[0-7]\d)|([1-9]?\d))(\.\d{1,6})?)$/ - return regex.test(value) + const regex = /^[-+]?(180(\.0{1,6})?|((1[0-7]\d)|([1-9]?\d))(\.\d{1,6})?)$/; + return regex.test(value); } /** * Check if any given value is a valid IP address. */ export function isIpAddress(value: string): boolean { - const regex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)((?::\d+)?|)$/ - return regex.test(value) + const regex = + /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)((?::\d+)?|)$/; + return regex.test(value); } /** * Check if any given value is a valid port number. */ export function isPort(value: number): boolean { - return value > 0 && value <= 65535 + return value > 0 && value <= 65535; } /** * Check if any given value is a valid MAC address. */ export function isMacAddress(value: string): boolean { - const regex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/ - return regex.test(value) + const regex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/; + return regex.test(value); } /** * Check if you're a passionate iPhone fan. */ export function isIos(): boolean { - return /iPad|iPhone|iPod/.test(navigator.platform) + return /iPad|iPhone|iPod/.test(navigator.platform); } /** * Check if you're a fervent Windows fan. */ export function isWindows(): boolean { - return /Win/.test(navigator.platform) + return /Win/.test(navigator.platform); } /** * Check if you're a devoted Linux fan. */ export function isLinux(): boolean { - return /Linux/.test(navigator.platform) + return /Linux/.test(navigator.platform); } /** * Check if you're a zealous Android fan. */ export function isAndroid(): boolean { - return /Android/.test(navigator.platform) + return /Android/.test(navigator.platform); } /** * Check if you're a staunch Mac fan. */ export function isMac(): boolean { - return /Mac/.test(navigator.platform) + return /Mac/.test(navigator.platform); } /** * Check if you're a die-hard Chrome fan. */ export function isChrome(): boolean { - return /Chrome/.test(navigator.userAgent) + return /Chrome/.test(navigator.userAgent); } /** * Check if you're a dedicated Firefox fan. */ export function isFirefox(): boolean { - return /Firefox/.test(navigator.userAgent) + return /Firefox/.test(navigator.userAgent); } /** * Check if you're a lonely Safari fan. */ export function isSafari(): boolean { - return /Safari/.test(navigator.userAgent) + return /Safari/.test(navigator.userAgent); } /** * Check if you're an ardent Edge fan. */ export function isEdge(): boolean { - return /Edge/.test(navigator.userAgent) + return /Edge/.test(navigator.userAgent); } /** * Check if you're rocking a mobile */ export function isMobile(): boolean { - return /Mobi/.test(navigator.userAgent) + return /Mobi/.test(navigator.userAgent); } /** * Check if you're tablet user */ export function isTablet(): boolean { - return /Tablet/.test(navigator.userAgent) + return /Tablet/.test(navigator.userAgent); } /** * Check if you're pro desktop user */ export function isDesktop(): boolean { - return !isMobile() && !isTablet() + return !isMobile() && !isTablet(); } /** * Check if you're portrait */ export function isPortrait(): boolean { - return window.innerHeight > window.innerWidth + return window.innerHeight > window.innerWidth; } /** * Check if you're landscape */ export function isLandscape(): boolean { - return window.innerWidth > window.innerHeight + return window.innerWidth > window.innerHeight; } /** * Check if you're a cyborg or a bot */ export function isBot(): boolean { - return /bot|googlebot|crawler|spider|robot|crawling/i.test(navigator.userAgent) + return /bot|googlebot|crawler|spider|robot|crawling/i.test( + navigator.userAgent + ); } From d3e22a34de2b43e4c9dbe31b3289eaad9a56df98 Mon Sep 17 00:00:00 2001 From: Jeremy Butler Date: Wed, 22 May 2024 11:14:43 +1000 Subject: [PATCH 07/41] Refactor splitByWords and add checkPasswordStrength tests --- nuxt-module/src/runtime/utils/config.ts | 8 -------- src/goodies.test.ts | 20 +++++++++++++++++++- src/goodies.ts | 12 ++++++++---- 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/nuxt-module/src/runtime/utils/config.ts b/nuxt-module/src/runtime/utils/config.ts index f4d5e02b..69e492ad 100644 --- a/nuxt-module/src/runtime/utils/config.ts +++ b/nuxt-module/src/runtime/utils/config.ts @@ -1,11 +1,3 @@ -export function convertToArray(value: string) { - try { - return JSON.parse(value) - } catch (error) { - return [] - } -} - export const currencySymbols = new Map([ ['en-US', 'USD'], // US Dollar ['en-GB', 'GBP'], // British Pound diff --git a/src/goodies.test.ts b/src/goodies.test.ts index 1c961e22..193e5d28 100644 --- a/src/goodies.test.ts +++ b/src/goodies.test.ts @@ -3,12 +3,30 @@ import * as mod from './goodies' test('splitByWords', () => { expect(mod.splitByWords('Hello world')).toBe( - 'Hello world' + 'Hello world' ) }) +test('checkPasswordStrength', () => { + expect(mod.checkPasswordStrength('hello')).toEqual({ score: 1, label: 'Password must be at least 8 characters long' }) + expect(mod.checkPasswordStrength('hello1234')).toEqual({ score: 1, label: 'Password must contain 1 uppercase letter' }) + expect(mod.checkPasswordStrength('Hello1234')).toEqual({ score: 1, label: 'Password must contain 1 special character' }) + expect(mod.checkPasswordStrength('Hello!!!!')).toEqual({ score: 1, label: 'Password must contain 1 number' }) + expect(mod.checkPasswordStrength('Hello1234#')).toEqual({ score: 4, label: 'Very Strong' }) + expect(mod.checkPasswordStrength('Hello1234#', { length: 10 })).toEqual({ score: 4, label: 'Very Strong' }) + expect(mod.checkPasswordStrength('Hello1234#', { length: 10, uppercase: 1 })).toEqual({ score: 4, label: 'Very Strong' }) + expect(mod.checkPasswordStrength('Hell0#', { length: 5 })).toEqual({ score: 3, label: 'Strong' }) + expect(mod.checkPasswordStrength('Hello1234#', { length: 10, uppercase: 1, number: 1 })).toEqual({ score: 4, label: 'Very Strong' }) + expect(mod.checkPasswordStrength('Hello1234#', { length: 10, uppercase: 1, number: 1, special: 1 })).toEqual({ score: 4, label: 'Very Strong' }) +}) + test('mergeFields', () => { expect(mod.mergeFields('The {{a}} said {{b}}', { a: 'cat', b: 'meow' })).toEqual('The cat said meow') expect(mod.mergeFields('The {{ a }} said {{ b }}', { a: 'cat', b: 'meow' })).toEqual('The cat said meow') + expect(mod.mergeFields('The {{ z }} said {{ t }}', { a: 'cat', b: 'meow' })).toEqual('The {{z}} said {{t}}') +}) + +test('readingTime', () => { + expect(mod.readingTime('Hello world', 200)).toBe('1 minute') }) diff --git a/src/goodies.ts b/src/goodies.ts index d7dd3ba7..3483cc4a 100644 --- a/src/goodies.ts +++ b/src/goodies.ts @@ -9,7 +9,11 @@ import { formatDurationLabels } from './formatters' * @info Don't forget to render the HTML safely. */ export function splitByWords(text: string): string { - const sentences = text.split(/([.?!]\s*)/) + if (!text) { + console.warn('[MODS] Warning: No text to split') + return '' + } + const sentences = text.split(/([.?!]+\s*)/) let wordIndex = 0 const combinedSentences = [] @@ -23,14 +27,14 @@ export function splitByWords(text: string): string { .split(' ') .map((word) => { wordIndex++ - return `${word}` + return `${word}` }) .join(' ') combinedSentences.push(`${words}`) } - return combinedSentences.join(' ') + return combinedSentences.join('') } /** @@ -52,7 +56,7 @@ export function checkPasswordStrength(value: string, options?: { length?: number if (counts.numbers < number) return { score: 1, label: `Password must contain ${number} number` } if (counts.special < special) return { score: 1, label: `Password must contain ${special} special character` } - if (value.length >= length) strength++ + if (value.length >= 8) strength++ if (counts.uppercase >= uppercase) strength++ if (counts.numbers >= number) strength++ if (counts.special >= special) strength++ From a7ad80215834612b7719b742c030d80d460b3946 Mon Sep 17 00:00:00 2001 From: Jeremy Butler Date: Wed, 22 May 2024 17:12:08 +1000 Subject: [PATCH 08/41] Refactor number formatting functions to handle decimal places dynamically --- .../content/formatters/FormatCurrency.vue | 4 ++-- .../content/formatters/FormatNumber.vue | 4 ++-- .../content/formatters/FormatValuation.vue | 2 +- src/formatters.ts | 20 ++++++++++--------- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/nuxt-web/components/content/formatters/FormatCurrency.vue b/nuxt-web/components/content/formatters/FormatCurrency.vue index b095a69a..03a8ebd0 100644 --- a/nuxt-web/components/content/formatters/FormatCurrency.vue +++ b/nuxt-web/components/content/formatters/FormatCurrency.vue @@ -7,9 +7,9 @@ - + - {{ formatCurrency(value, { decimals, locale }) }} + {{ formatCurrency(value, { ...(decimals ? { decimals } : {}), locale }) }} diff --git a/nuxt-web/components/content/formatters/FormatNumber.vue b/nuxt-web/components/content/formatters/FormatNumber.vue index b00c8917..62be16a6 100644 --- a/nuxt-web/components/content/formatters/FormatNumber.vue +++ b/nuxt-web/components/content/formatters/FormatNumber.vue @@ -7,9 +7,9 @@ - + - {{ formatNumber(currency, { decimals, locale }) }} + {{ formatNumber(currency, { ...(decimals ? { decimals } : {}), locale }) }} diff --git a/nuxt-web/components/content/formatters/FormatValuation.vue b/nuxt-web/components/content/formatters/FormatValuation.vue index 0ce05d2a..f7fada4e 100644 --- a/nuxt-web/components/content/formatters/FormatValuation.vue +++ b/nuxt-web/components/content/formatters/FormatValuation.vue @@ -16,6 +16,6 @@ diff --git a/src/formatters.ts b/src/formatters.ts index 9105b075..ffbca221 100644 --- a/src/formatters.ts +++ b/src/formatters.ts @@ -8,27 +8,29 @@ import { currencySymbols, numberUnderTwenty, numberTens, numberScales } from './ * Format numbers into neat and formatted strings for people */ export function formatNumber(number: number, options?: { decimals?: number; locale?: string }): string { - const safeDecimals = Math.max(0, Math.min(options?.decimals ?? 2, 20)) + const decimalPlaces = (number.toString().split('.')[1] || '').length; + const safeDecimals = Math.max(0, Math.min(options?.decimals ?? decimalPlaces, decimalPlaces)); const config: Intl.NumberFormatOptions = { style: 'decimal', - minimumFractionDigits: safeDecimals === 0 ? 0 : safeDecimals === 1 ? 1 : 2, - maximumFractionDigits: safeDecimals - } + minimumFractionDigits: safeDecimals, + maximumFractionDigits: safeDecimals, + }; - return new Intl.NumberFormat(options?.locale ?? 'en-US', config).format(number) + return new Intl.NumberFormat(options?.locale ?? 'en-US', config).format(number); } /** * Format numbers into local currency with extra smarts */ export function formatCurrency(number: number, options?: { decimals?: number; locale?: string }): string { - const safeDecimals = Math.max(0, Math.min(options?.decimals ?? 2, 20)) + const decimalPlaces = (number.toString().split('.')[1] || '').length; + const safeDecimals = Math.max(0, Math.min(options?.decimals ?? decimalPlaces, decimalPlaces)); const config: Intl.NumberFormatOptions = { style: 'currency', currencyDisplay: 'narrowSymbol', - minimumFractionDigits: safeDecimals === 0 ? 0 : safeDecimals === 1 ? 1 : 2, + minimumFractionDigits: safeDecimals, maximumFractionDigits: safeDecimals, currency: currencySymbols.get(options?.locale ?? 'en-US') || 'USD' } @@ -40,14 +42,14 @@ export function formatCurrency(number: number, options?: { decimals?: number; lo * Format numbers into valuations displayed in thousands, millions or billions */ export function formatValuation(number: number, options?: { decimals?: number; locale?: string }): string { - const safeDecimals = Math.max(0, Math.min(options?.decimals ?? 2, 20)) + const safeDecimals = Math.max(0, Math.min(options?.decimals ?? 0, 20)) const config: Intl.NumberFormatOptions = { style: 'currency', currencyDisplay: 'narrowSymbol', notation: 'compact', compactDisplay: 'short', - minimumFractionDigits: safeDecimals === 0 ? 0 : safeDecimals === 1 ? 1 : 2, + minimumFractionDigits: safeDecimals, maximumFractionDigits: safeDecimals, currency: currencySymbols.get(options?.locale ?? 'en-US') || 'USD' } From 7f5ec25708e32a500b8e0b9ee41f0237f8de3fe2 Mon Sep 17 00:00:00 2001 From: Jeremy Butler Date: Wed, 22 May 2024 17:12:13 +1000 Subject: [PATCH 09/41] Update Callout component styles --- nuxt-web/components/Callout.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nuxt-web/components/Callout.vue b/nuxt-web/components/Callout.vue index 57cb204d..340ad42e 100644 --- a/nuxt-web/components/Callout.vue +++ b/nuxt-web/components/Callout.vue @@ -1,6 +1,6 @@ From c99fd9303d799dc6a91e07594ed5d36205434c56 Mon Sep 17 00:00:00 2001 From: Jeremy Butler Date: Wed, 22 May 2024 17:12:18 +1000 Subject: [PATCH 10/41] Add number formatting and password strength tests --- src/formatters.test.ts | 2 ++ src/goodies.test.ts | 16 +++++++++++++++- src/goodies.ts | 6 +++--- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/formatters.test.ts b/src/formatters.test.ts index 0f119992..48ffd1ca 100644 --- a/src/formatters.test.ts +++ b/src/formatters.test.ts @@ -4,6 +4,8 @@ import * as mod from './formatters' test('formatNumber', () => { expect(mod.formatNumber(1000.95)).toBe('1,000.95') expect(mod.formatNumber(1000.95, { decimals: 2 })).toBe('1,000.95') + expect(mod.formatNumber(1000.95, { decimals: 0 })).toBe('1,001') + expect(mod.formatNumber(1000.95, { decimals: 1 })).toBe('1,001.0') expect(mod.formatNumber(1000.95, { decimals: 2, locale: 'id-ID' })).toBe('1.000,95') }) diff --git a/src/goodies.test.ts b/src/goodies.test.ts index 193e5d28..f83b8165 100644 --- a/src/goodies.test.ts +++ b/src/goodies.test.ts @@ -5,19 +5,33 @@ test('splitByWords', () => { expect(mod.splitByWords('Hello world')).toBe( 'Hello world' ) + expect(mod.splitByWords('Hello world! This is a test. ')).toBe( + 'Hello world! This is a test. ' + ) + expect(mod.splitByWords('')).toBe('') }) test('checkPasswordStrength', () => { + // Basic checks expect(mod.checkPasswordStrength('hello')).toEqual({ score: 1, label: 'Password must be at least 8 characters long' }) expect(mod.checkPasswordStrength('hello1234')).toEqual({ score: 1, label: 'Password must contain 1 uppercase letter' }) expect(mod.checkPasswordStrength('Hello1234')).toEqual({ score: 1, label: 'Password must contain 1 special character' }) expect(mod.checkPasswordStrength('Hello!!!!')).toEqual({ score: 1, label: 'Password must contain 1 number' }) expect(mod.checkPasswordStrength('Hello1234#')).toEqual({ score: 4, label: 'Very Strong' }) + + // Custom length checks + expect(mod.checkPasswordStrength('Hell0#', { length: 5 })).toEqual({ score: 3, label: 'Strong' }) expect(mod.checkPasswordStrength('Hello1234#', { length: 10 })).toEqual({ score: 4, label: 'Very Strong' }) expect(mod.checkPasswordStrength('Hello1234#', { length: 10, uppercase: 1 })).toEqual({ score: 4, label: 'Very Strong' }) - expect(mod.checkPasswordStrength('Hell0#', { length: 5 })).toEqual({ score: 3, label: 'Strong' }) expect(mod.checkPasswordStrength('Hello1234#', { length: 10, uppercase: 1, number: 1 })).toEqual({ score: 4, label: 'Very Strong' }) expect(mod.checkPasswordStrength('Hello1234#', { length: 10, uppercase: 1, number: 1, special: 1 })).toEqual({ score: 4, label: 'Very Strong' }) + + // Custom criteria checks + expect(mod.checkPasswordStrength('hell', { length: 1, uppercase: 0, number: 0, special: 0 })).toEqual({ score: 0, label: 'Very Weak' }) + expect(mod.checkPasswordStrength('Hell', { length: 1, uppercase: 0, number: 0, special: 0 })).toEqual({ score: 1, label: 'Weak' }) + expect(mod.checkPasswordStrength('Hell0', { length: 1, uppercase: 0, number: 0, special: 0 })).toEqual({ score: 2, label: 'Medium' }) + expect(mod.checkPasswordStrength('Hell0#', { length: 1, uppercase: 0, number: 0, special: 0 })).toEqual({ score: 3, label: 'Strong' }) + expect(mod.checkPasswordStrength('Heeeeell0#', { length: 1, uppercase: 0, number: 0, special: 0 })).toEqual({ score: 4, label: 'Very Strong' }) }) diff --git a/src/goodies.ts b/src/goodies.ts index 3483cc4a..9050ffdb 100644 --- a/src/goodies.ts +++ b/src/goodies.ts @@ -57,9 +57,9 @@ export function checkPasswordStrength(value: string, options?: { length?: number if (counts.special < special) return { score: 1, label: `Password must contain ${special} special character` } if (value.length >= 8) strength++ - if (counts.uppercase >= uppercase) strength++ - if (counts.numbers >= number) strength++ - if (counts.special >= special) strength++ + if (counts.uppercase >= 1) strength++ + if (counts.numbers >= 1) strength++ + if (counts.special >= 1) strength++ if (strength === 4) return { score: 4, label: 'Very Strong' } if (strength === 3) return { score: 3, label: 'Strong' } From c9937fc53f0dfc07d1deea21a769aa5a75c99982 Mon Sep 17 00:00:00 2001 From: Jeremy Butler Date: Wed, 22 May 2024 17:16:26 +1000 Subject: [PATCH 11/41] Refactor formatCurrency function to simplify options handling --- nuxt-web/components/content/formatters/FormatCurrency.vue | 4 ++-- src/formatters.ts | 8 +++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/nuxt-web/components/content/formatters/FormatCurrency.vue b/nuxt-web/components/content/formatters/FormatCurrency.vue index 03a8ebd0..b095a69a 100644 --- a/nuxt-web/components/content/formatters/FormatCurrency.vue +++ b/nuxt-web/components/content/formatters/FormatCurrency.vue @@ -7,9 +7,9 @@ - + - {{ formatCurrency(value, { ...(decimals ? { decimals } : {}), locale }) }} + {{ formatCurrency(value, { decimals, locale }) }} diff --git a/src/formatters.ts b/src/formatters.ts index ffbca221..39f9d8f2 100644 --- a/src/formatters.ts +++ b/src/formatters.ts @@ -24,14 +24,12 @@ export function formatNumber(number: number, options?: { decimals?: number; loca * Format numbers into local currency with extra smarts */ export function formatCurrency(number: number, options?: { decimals?: number; locale?: string }): string { - const decimalPlaces = (number.toString().split('.')[1] || '').length; - const safeDecimals = Math.max(0, Math.min(options?.decimals ?? decimalPlaces, decimalPlaces)); - + const config: Intl.NumberFormatOptions = { style: 'currency', currencyDisplay: 'narrowSymbol', - minimumFractionDigits: safeDecimals, - maximumFractionDigits: safeDecimals, + minimumFractionDigits: options?.decimals ?? 2, + maximumFractionDigits: options?.decimals ?? 2, currency: currencySymbols.get(options?.locale ?? 'en-US') || 'USD' } From 3f2bca4cfc1a7062b9a58b7cf4c7e8ce53cd011b Mon Sep 17 00:00:00 2001 From: Jeremy Butler Date: Wed, 22 May 2024 17:30:20 +1000 Subject: [PATCH 12/41] Refactor number formatting and unit display in FormatUnit.vue and formatters.ts --- .../content/formatters/FormatUnit.vue | 39 ++++++++++--------- src/formatters.test.ts | 4 +- src/formatters.ts | 15 +++---- 3 files changed, 30 insertions(+), 28 deletions(-) diff --git a/nuxt-web/components/content/formatters/FormatUnit.vue b/nuxt-web/components/content/formatters/FormatUnit.vue index 5d66771c..6ff942f4 100644 --- a/nuxt-web/components/content/formatters/FormatUnit.vue +++ b/nuxt-web/components/content/formatters/FormatUnit.vue @@ -1,24 +1,25 @@ From a697aa4b760e4588b7644b5d1391e8a920e8caad Mon Sep 17 00:00:00 2001 From: Jeremy Butler Date: Wed, 22 May 2024 19:05:44 +1000 Subject: [PATCH 19/41] Refactor number formatting functions --- src/formatters.ts | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/formatters.ts b/src/formatters.ts index d007d853..40881400 100644 --- a/src/formatters.ts +++ b/src/formatters.ts @@ -9,7 +9,7 @@ import { currencySymbols, numberUnderTwenty, numberTens, numberScales, formatTit */ export function formatNumber(number: number, options?: { decimals?: number; locale?: string }): string { const decimalPlaces = (number.toString().split('.')[1] || '').length; - const safeDecimals = Math.max(0, Math.min(options?.decimals ?? decimalPlaces, decimalPlaces)); + const safeDecimals = options?.decimals ?? decimalPlaces; const config: Intl.NumberFormatOptions = { style: 'decimal', @@ -55,26 +55,12 @@ export function formatValuation(number: number, options?: { decimals?: number; l return new Intl.NumberFormat(options?.locale ?? 'en-US', config).format(number) } -/** - * Format a number into a percentage - */ -export function formatPercentage(number: number, options?: { decimals?: number; locale?: string }): string { - const safeDecimals = Math.max(0, Math.min(options?.decimals ?? 2, 20)) - const config: Intl.NumberFormatOptions = { - style: 'percent', - minimumFractionDigits: safeDecimals === 0 ? 0 : safeDecimals === 1 ? 1 : 2, - maximumFractionDigits: safeDecimals - } - - return new Intl.NumberFormat(options?.locale ?? 'en-US', config).format(number) -} - /** * Format a number into a your unit of choice */ export function formatUnit(number: number, options: { unit: string; decimals?: number; unitDisplay?: 'short' | 'long'; locale?: string }): string { const decimalPlaces = (number.toString().split('.')[1] || '').length; - const safeDecimals = Math.max(0, Math.min(options?.decimals ?? options.decimals ?? decimalPlaces, decimalPlaces)); + const safeDecimals = options?.decimals ?? decimalPlaces; const config: Intl.NumberFormatOptions = { unit: options.unit, style: 'unit', @@ -86,6 +72,20 @@ export function formatUnit(number: number, options: { unit: string; decimals?: n return new Intl.NumberFormat(options.locale ?? 'en-US', config).format(number) } +/** + * Format a number into a percentage + */ +export function formatPercentage(number: number, options?: { decimals?: number; locale?: string }): string { + const safeDecimals = Math.max(0, Math.min(options?.decimals ?? 2, 20)) + const config: Intl.NumberFormatOptions = { + style: 'percent', + minimumFractionDigits: safeDecimals === 0 ? 0 : safeDecimals === 1 ? 1 : 2, + maximumFractionDigits: safeDecimals + } + + return new Intl.NumberFormat(options?.locale ?? 'en-US', config).format(number) +} + /** * Format time into a human-readable string */ From 302e41cfd5e6c254e8a643113c63150de033cacf Mon Sep 17 00:00:00 2001 From: Jeremy Butler Date: Wed, 22 May 2024 20:24:20 +1000 Subject: [PATCH 20/41] Add new formatting functions and tests --- src/formatters.test.ts | 21 ++++++++++++++++++++- src/formatters.ts | 25 ++++++++++++++++--------- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/src/formatters.test.ts b/src/formatters.test.ts index ebe1aa16..51048d99 100644 --- a/src/formatters.test.ts +++ b/src/formatters.test.ts @@ -2,6 +2,7 @@ import { expect, test } from 'vitest' import * as mod from './formatters' test('formatNumber', () => { + expect(mod.formatNumber(0)).toBe('0') expect(mod.formatNumber(1000.95)).toBe('1,000.95') expect(mod.formatNumber(1000.95, { decimals: 2 })).toBe('1,000.95') expect(mod.formatNumber(1000.95, { decimals: 0 })).toBe('1,001') @@ -30,6 +31,7 @@ test('formatValuation', () => { test('formatDurationLabels', () => { expect(mod.formatDurationLabels(0)).toBe('0 seconds') + expect(mod.formatDurationLabels(0, { labels: 'short' })).toBe('0 sec') expect(mod.formatDurationLabels(0.005)).toBe('5 milliseconds') expect(mod.formatDurationLabels(0.5)).toBe('500 milliseconds') expect(mod.formatDurationLabels(3600)).toBe('1 hour') @@ -38,15 +40,29 @@ test('formatDurationLabels', () => { expect(mod.formatDurationLabels(3600 * 2 + 60)).toBe('2 hours 1 minute') expect(mod.formatDurationLabels(3600 * 2 + 60 + 1.5)).toBe('2 hours 1 minute 1 second 500 milliseconds') expect(mod.formatDurationLabels(3600 * 400 + 60 + 1)).toBe('16 days 16 hours 1 minute 1 second') + expect(mod.formatDurationLabels(3600 * 400 + 60 + 1, { round: true })).toBe('16 days 16 hours 1 minute 1 second') +}) + +test('formatDurationNumbers', () => { + expect(mod.formatDurationNumbers(0)).toBe('00:00:00') + expect(mod.formatDurationNumbers(0.5)).toBe('00:00:00:50') + expect(mod.formatDurationNumbers(3600)).toBe('01:00:00') + expect(mod.formatDurationNumbers(3600 * 2)).toBe('02:00:00') + expect(mod.formatDurationNumbers(3600 * 2 + 60)).toBe('02:01:00') + expect(mod.formatDurationNumbers(3600 * 2 + 60 + 1.5)).toBe('02:01:01:50') + expect(mod.formatDurationNumbers(3600 * 400 + 60 + 1)).toBe('400:01:01') + expect(mod.formatDurationNumbers(3600 * 400 + 60 + 1)).toBe('400:01:01') }) test('formatPercentage', () => { + expect(mod.formatPercentage(0)).toBe('0.00%') expect(mod.formatPercentage(0.1234, { decimals: 0 })).toBe('12%') - expect(mod.formatPercentage(0.1234, { decimals: 2 })).toBe('12.34%') + expect(mod.formatPercentage(0.1234)).toBe('12.34%') expect(mod.formatPercentage(0.125, { decimals: 0 })).toBe('13%') }) test('formatUnit', () => { + expect(mod.formatUnit(0, { unit: 'meter' })).toBe('0 meters') expect(mod.formatUnit(1000, { unit: 'meter', decimals: 0 })).toBe('1,000 meters') expect(mod.formatUnit(1000, { unit: 'meter', decimals: 0 })).toBe('1,000 meters') expect(mod.formatUnit(1000, { unit: 'meter', decimals: 2, unitDisplay: 'short' })).toBe('1,000.00 m') @@ -81,6 +97,9 @@ test('formatNumberToWord', () => { expect(mod.formatNumberToWords(0)).toBe('zero') expect(mod.formatNumberToWords(1)).toBe('one') expect(mod.formatNumberToWords(12)).toBe('twelve') + expect(mod.formatNumberToWords(100)).toBe('one hundred') + expect(mod.formatNumberToWords(200)).toBe('two hundred') + expect(mod.formatNumberToWords(300)).toBe('three hundred') expect(mod.formatNumberToWords(123)).toBe('one hundred and twenty-three') expect(mod.formatNumberToWords(1234)).toBe('one thousand, two hundred and thirty-four') expect(mod.formatNumberToWords(12345)).toBe('twelve thousand, three hundred and forty-five') diff --git a/src/formatters.ts b/src/formatters.ts index 40881400..8a1b9867 100644 --- a/src/formatters.ts +++ b/src/formatters.ts @@ -49,7 +49,7 @@ export function formatValuation(number: number, options?: { decimals?: number; l compactDisplay: 'short', minimumFractionDigits: safeDecimals, maximumFractionDigits: safeDecimals, - currency: currencySymbols.get(options?.locale ?? 'en-US') || 'USD' + currency: currencySymbols.get(options?.locale ?? 'en-US') } return new Intl.NumberFormat(options?.locale ?? 'en-US', config).format(number) @@ -79,7 +79,7 @@ export function formatPercentage(number: number, options?: { decimals?: number; const safeDecimals = Math.max(0, Math.min(options?.decimals ?? 2, 20)) const config: Intl.NumberFormatOptions = { style: 'percent', - minimumFractionDigits: safeDecimals === 0 ? 0 : safeDecimals === 1 ? 1 : 2, + minimumFractionDigits: safeDecimals, maximumFractionDigits: safeDecimals } @@ -121,19 +121,26 @@ export function formatDurationLabels(seconds: number, options?: { labels?: 'shor * Format time into duration 00:00:00 */ export function formatDurationNumbers(seconds: number): string { - const h = Math.floor(seconds / 3600) - const m = Math.floor((seconds - h * 3600) / 60) - const s = seconds - h * 3600 - m * 60 + const h = Math.floor(seconds / 3600); + const m = Math.floor((seconds % 3600) / 60); + const s = Math.floor(seconds % 60); + const ms = Math.floor((seconds % 1) * 1000); + + const timeParts = [h, m, s].map((value) => value.toString().padStart(2, '0')); + + if (ms > 0) { + const msString = Math.floor(ms / 10).toString().padStart(2, '0'); + timeParts.push(msString); + } - return [h, m, s].map((value) => value.toString().padStart(2, '0')).join(':') + return timeParts.join(':'); } /** * Format numbers into words */ export function formatNumberToWords(number: number): string { - if (number < 20) return numberUnderTwenty[number]; - if (number < 100) return `${numberTens[Math.floor(number / 10) - 2]}${number % 10 ? '-' + numberUnderTwenty[number % 10] : ''}`; + if (number === 0) return numberUnderTwenty[0]; const formatGroup = (num: number): string => { if (num < 20) return numberUnderTwenty[num]; @@ -147,7 +154,7 @@ export function formatNumberToWords(number: number): string { while (number > 0) { const groupValue = number % 1000; if (groupValue > 0) { - result = formatGroup(groupValue) + numberScales[scaleIndex] + (result ? ', ' + result : ''); + result = `${formatGroup(groupValue)}${numberScales[scaleIndex]}${result ? ', ' + result : ''}`; } number = Math.floor(number / 1000); scaleIndex++; From 8792419845af16f6e330764e5688425ecf6bfe2c Mon Sep 17 00:00:00 2001 From: Jeremy Butler Date: Wed, 22 May 2024 20:43:25 +1000 Subject: [PATCH 21/41] Add new formatter tests --- src/formatters.test.ts | 29 ++++++++++++++++++++++++++++- src/formatters.ts | 8 ++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/formatters.test.ts b/src/formatters.test.ts index 51048d99..c4531e2a 100644 --- a/src/formatters.test.ts +++ b/src/formatters.test.ts @@ -71,7 +71,9 @@ test('formatUnit', () => { }) test('formatList', () => { - expect(mod.formatList(['Apple', 'Oranges'])).toBe('Apple and Oranges') + expect(mod.formatList(['Apple'])).toBe('Apple') + expect(mod.formatList('Apple, Oranges')).toBe('Apple and Oranges') + expect(mod.formatList({'0': 'Apple', '1': 'Oranges'})).toBe('Apple and Oranges') expect(mod.formatList(['Apple', 'Oranges', 'Bananas', 'Grapefruit'])).toBe('Apple, Oranges, Bananas and Grapefruit') expect(mod.formatList(['Apple', 'Oranges'], { limit: 2 })).toBe('Apple and Oranges') expect(mod.formatList(['Apple', 'Oranges', 'Bananas'], { limit: 2 })).toBe('Apple, Oranges and 1 more') @@ -116,6 +118,9 @@ test('formatNumberToWord', () => { }) test('formatUnixTime', () => { + expect(mod.formatUnixTime(0)).toBe('1970-01-01 00:00:00.000') + expect(mod.formatUnixTime(-10)).toBe('-10') + expect(mod.formatUnixTime(1619999999)).toBe('2021-05-02 23:59:59.000') expect(mod.formatUnixTime(1620000000)).toBe('2021-05-03 00:00:00.000') }) @@ -132,3 +137,25 @@ test('formatInitials', () => { // @ts-expect-error: null is not a valid input for formatInitials expect(mod.formatInitials(null)).toBe('') }) + +test('formatSentenceCase', () => { + expect(mod.formatSentenceCase('')).toBe('') + expect(mod.formatSentenceCase('hello world')).toBe('Hello world') + expect(mod.formatSentenceCase('welcome to the jungle')).toBe('Welcome to the jungle') + expect(mod.formatSentenceCase('the quick brown fox jumps over the lazy dog')).toBe('The quick brown fox jumps over the lazy dog') + expect(mod.formatSentenceCase('UseMods is cooler than a vegan leather jacket')).toBe('UseMods is cooler than a vegan leather jacket') + // @ts-expect-error: null is not a valid input for formatSentenceCase + expect(mod.formatSentenceCase(null)).toBe('') + // @ts-expect-error: undefined is not a valid input for formatSentenceCase + expect(mod.formatSentenceCase(undefined)).toBe('') +}) + +test('formatTextWrap', () => { + expect(mod.formatTextWrap('')).toBe('') + expect(mod.formatTextWrap('hello world')).toBe('hello world') + expect(mod.formatTextWrap('hello world how are you')).toBe('hello world how are you') + expect(mod.formatTextWrap('This is a test')).toBe('This is a test'); + expect(mod.formatTextWrap('Test')).toBe('Test'); + expect(mod.formatTextWrap('TestTest')).toBe('TestTest'); + expect(mod.formatTextWrap('')).toBe(''); +}) diff --git a/src/formatters.ts b/src/formatters.ts index 8a1b9867..ba10da2b 100644 --- a/src/formatters.ts +++ b/src/formatters.ts @@ -228,6 +228,10 @@ export function formatTitle(text: string): string { * Format a sentence case string */ export function formatSentenceCase(text: string): string { + if (!text) { + console.warn('[MODS] Empty formatSentenceCase text') + return '' + } return text .split('\n\n') .map((paragraph) => @@ -244,6 +248,10 @@ export function formatSentenceCase(text: string): string { * @info Remember `text-wrap: pretty` and `text-wrap: balance` are available for most browsers. */ export function formatTextWrap(text: string): string { + if (!text) { + console.warn('[MODS] Empty formatTextWrap text') + return '' + } const space = text.lastIndexOf(' ') if (space !== -1) return text.substring(0, space) + ' ' + text.substring(space + 1) return text From 6f29f0cf1cd671f07d18fe33c857e756bfb0b37c Mon Sep 17 00:00:00 2001 From: Jeremy Butler Date: Wed, 22 May 2024 20:51:17 +1000 Subject: [PATCH 22/41] Increase goodies test coverage to 100% --- src/goodies.test.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/goodies.test.ts b/src/goodies.test.ts index f83b8165..3abe5597 100644 --- a/src/goodies.test.ts +++ b/src/goodies.test.ts @@ -12,6 +12,8 @@ test('splitByWords', () => { }) test('checkPasswordStrength', () => { + // Empty + expect(mod.checkPasswordStrength('')).toEqual({ score: 0, label: 'Very Weak' }) // Basic checks expect(mod.checkPasswordStrength('hello')).toEqual({ score: 1, label: 'Password must be at least 8 characters long' }) expect(mod.checkPasswordStrength('hello1234')).toEqual({ score: 1, label: 'Password must contain 1 uppercase letter' }) @@ -36,11 +38,17 @@ test('checkPasswordStrength', () => { test('mergeFields', () => { + // Empty + expect(mod.mergeFields('', {})).toEqual('') + // Basic checks expect(mod.mergeFields('The {{a}} said {{b}}', { a: 'cat', b: 'meow' })).toEqual('The cat said meow') expect(mod.mergeFields('The {{ a }} said {{ b }}', { a: 'cat', b: 'meow' })).toEqual('The cat said meow') expect(mod.mergeFields('The {{ z }} said {{ t }}', { a: 'cat', b: 'meow' })).toEqual('The {{z}} said {{t}}') }) test('readingTime', () => { + // Empty + expect(mod.readingTime('')).toBe('0 minutes') + // Basic checks expect(mod.readingTime('Hello world', 200)).toBe('1 minute') }) From 9805e5b9e9b2148554a8931e4728df2b9dd496a6 Mon Sep 17 00:00:00 2001 From: Jeremy Butler Date: Wed, 22 May 2024 21:31:58 +1000 Subject: [PATCH 23/41] Refactor string manipulation functions --- src/modifiers.test.ts | 168 +++++++++------------------------ src/modifiers.ts | 36 ++++--- src/validators.ts | 212 +++++++++++++++++++++--------------------- 3 files changed, 173 insertions(+), 243 deletions(-) diff --git a/src/modifiers.test.ts b/src/modifiers.test.ts index e1375ef7..d3d3edbf 100644 --- a/src/modifiers.test.ts +++ b/src/modifiers.test.ts @@ -19,6 +19,11 @@ test('endWith', () => { test('endWithout', () => { expect(mod.endWithout('www.helloworld.com', '.com')).toBe('www.helloworld') expect(mod.endWithout('helloworld.com', '.com')).toBe('helloworld') + expect(mod.endWithout('filename.txt', '.txt')).toBe('filename'); + expect(mod.endWithout('filename.txt', '.jpg')).toBe('filename.txt'); + expect(mod.endWithout('', '.txt')).toBe(''); + expect(mod.endWithout('filename.txt', '')).toBe('filename.txt'); + expect(mod.endWithout('.txt', '.txt')).toBe(''); }) test('surroundWith', () => { @@ -29,30 +34,28 @@ test('surroundWith', () => { }) test('pluralize', () => { + expect(mod.pluralize('cat', 2)).toBe('cats') + expect(mod.pluralize('dog', 1)).toBe('dog') + expect(mod.pluralize('bus', 2)).toBe('buses') + expect(mod.pluralize('box', 2)).toBe('boxes') + expect(mod.pluralize('church', 2)).toBe('churches') + expect(mod.pluralize('baby', 2)).toBe('babies') + expect(mod.pluralize('leaf', 2)).toBe('leaves') + expect(mod.pluralize('life', 2)).toBe('lives') + expect(mod.pluralize('potato', 2)).toBe('potatoes') + expect(mod.pluralize('cactus', 2)).toBe('cacti') + // unchanging plural expect(mod.pluralize('sheep', 0)).toBe('sheep') expect(mod.pluralize('sheep', 1)).toBe('sheep') expect(mod.pluralize('sheep', 2)).toBe('sheep') - // Geez I'm getting sleepy - expect(mod.pluralize('apple', 0)).toBe('apples') - expect(mod.pluralize('apple', 2)).toBe('apples') - expect(mod.pluralize('box', 2)).toBe('boxes') - expect(mod.pluralize('bus', 2)).toBe('buses') + // irregular plural expect(mod.pluralize('child', 2)).toBe('children') - expect(mod.pluralize('goose', 2)).toBe('geese') - expect(mod.pluralize('leaf', 2)).toBe('leaves') - expect(mod.pluralize('knife', 2)).toBe('knives') - expect(mod.pluralize('city', 2)).toBe('cities') - expect(mod.pluralize('bus', 2)).toBe('buses') - expect(mod.pluralize('kiss', 2)).toBe('kisses') - expect(mod.pluralize('box', 2)).toBe('boxes') - expect(mod.pluralize('church', 2)).toBe('churches') - expect(mod.pluralize('brush', 2)).toBe('brushes') - expect(mod.pluralize('dress', 2)).toBe('dresses') }) test('singularize', () => { expect(mod.singularize('sheep')).toBe('sheep') expect(mod.singularize('apples')).toBe('apple') + expect(mod.singularize('apple')).toBe('apple') expect(mod.singularize('boxes')).toBe('box') expect(mod.singularize('buses')).toBe('bus') expect(mod.singularize('children')).toBe('child') @@ -119,117 +122,34 @@ test('ordinalize', () => { expect(mod.ordinalize(2)).toBe('2nd') expect(mod.ordinalize(3)).toBe('3rd') expect(mod.ordinalize(4)).toBe('4th') - expect(mod.ordinalize(11)).toBe('11th') - expect(mod.ordinalize(12)).toBe('12th') - expect(mod.ordinalize(13)).toBe('13th') - expect(mod.ordinalize(14)).toBe('14th') - expect(mod.ordinalize(21)).toBe('21st') - expect(mod.ordinalize(22)).toBe('22nd') - expect(mod.ordinalize(23)).toBe('23rd') - expect(mod.ordinalize(24)).toBe('24th') - expect(mod.ordinalize(101)).toBe('101st') - expect(mod.ordinalize(102)).toBe('102nd') - expect(mod.ordinalize(103)).toBe('103rd') expect(mod.ordinalize(104)).toBe('104th') }) +test('camelCase', () => { + expect(mod.camelCase('')).toBe('') + expect(mod.camelCase('Hello world')).toBe('helloWorld') + expect(mod.camelCase('Hello world! This is a test. ')).toBe('helloWorldThisIsATest') +}) +test('pascalCase', () => { + expect(mod.pascalCase('')).toBe('') + expect(mod.pascalCase('Hello world')).toBe('HelloWorld') + expect(mod.pascalCase('Hello world! This is a test. ')).toBe('HelloWorldThisIsATest') +}) + +test('snakeCase', () => { + expect(mod.snakeCase('')).toBe('') + expect(mod.snakeCase('Hello world')).toBe('hello_world') + expect(mod.snakeCase('Hello world! This is a test. ')).toBe('hello_world_this_is_a_test') +}) + +test('kebabCase', () => { + expect(mod.kebabCase('')).toBe('') + expect(mod.kebabCase('Hello world')).toBe('hello-world') + expect(mod.kebabCase('Hello world! This is a test. ')).toBe('hello-world-this-is-a-test') +}) + +test('titleCase', () => { + expect(mod.titleCase('hello world')).toBe('Hello World') +}) -// test('group', () => { -// const items = [ -// { name: 'John', age: 25 }, -// { name: 'Jane', age: 30 }, -// { name: 'Jake', age: 25 } -// ] - -// expect(mod.group(items, 'age')).toStrictEqual({ -// 25: [ -// { name: 'John', age: 25 }, -// { name: 'Jake', age: 25 } -// ], -// 30: [{ name: 'Jane', age: 30 }] -// }) - -// expect(mod.group(items, 'name')).toStrictEqual({ -// John: [{ name: 'John', age: 25 }], -// Jane: [{ name: 'Jane', age: 30 }], -// Jake: [{ name: 'Jake', age: 25 }] -// }) -// }) - -// test('groupBy', () => { -// const chunk = ['a', 'b', 'c', 'd', 'e', 'f'] -// expect(mod.groupBy(chunk, 2)).toStrictEqual([ -// ['a', 'b'], -// ['c', 'd'], -// ['e', 'f'] -// ]) -// expect(mod.groupBy(chunk, 3)).toStrictEqual([ -// ['a', 'b', 'c'], -// ['d', 'e', 'f'] -// ]) -// expect(mod.groupBy(chunk, 4)).toStrictEqual([ -// ['a', 'b', 'c', 'd'], -// ['e', 'f'] -// ]) -// expect(mod.groupBy(chunk, 5)).toStrictEqual([['a', 'b', 'c', 'd', 'e'], ['f']]) -// expect(mod.groupBy(chunk, 6)).toStrictEqual([chunk]) -// }) - -// test('flatten', () => { -// expect(mod.flatten([1, [2, [3, [4]], 5]])).toStrictEqual([1, 2, 3, 4, 5]) -// }) - -// test('without', () => { -// expect(mod.without([1, 2, 3, 4, 5], [2, 4])).toStrictEqual([1, 3, 5]) -// }) - -// test('combine', () => { -// expect(mod.combine([1, 2, 3], [2, 3, 4])).toStrictEqual([1, 2, 3, 2, 3, 4]) -// expect(mod.combine([1, 2, 3], [2, 3, 4], [3, 4, 5])).toStrictEqual([1, 2, 3, 2, 3, 4, 3, 4, 5]) -// }) - -// test('combineUnique', () => { -// expect(mod.combineUnique([1, 2, 3], [2, 3, 4])).toStrictEqual([1, 2, 3, 4]) -// expect(mod.combineUnique([1, 2, 3], [2, 3, 4], [3, 4, 5])).toStrictEqual([1, 2, 3, 4, 5]) -// }) - -// test('combineWithout', () => { -// expect(mod.combineWithout('id', { id: 1, name: 'A' }, { id: 2, name: 'B' }, { id: 3, name: 'C' })).toStrictEqual([1, 2, 3]) -// expect(mod.combineWithout('name', { id: 1, name: 'A' }, { id: 2, name: 'B' }, { id: 3, name: 'C' })).toStrictEqual(['A', 'B', 'C']) -// }) - -// test('reverse', () => { -// expect(mod.reverse([1, 2, 3])).toStrictEqual([3, 2, 1]) -// }) - -// test.skip('difference', () => { -// expect(mod.difference(['one', 'two', 'three'], ['one', 'two'])).toStrictEqual(['three']) -// expect(mod.difference([1, 2, 3], [2, 3, 4])).toStrictEqual([1, 4]) -// expect(mod.difference([1, 2, 3], [2, 3, 4], [3, 4, 5])).toStrictEqual([1, 5]) -// }) - -// test('title', () => { -// expect(mod.title('A new welcome for my brand new test for titles in javascript!')).toBe('A New Welcome for My Brand New Test for Titles in Javascript!') -// }) - -// test('truncate', () => { -// expect(mod.truncate('Hello world this is me', 4)).toBe('Hell...') -// }) - -// test('truncateWords', () => { -// expect(mod.truncateWords('Hello world and moon', 2)).toBe('Hello world...') -// }) - -// test('countWords', () => { -// expect(mod.countWords('Hello world and moon')).toBe(4) -// }) - -// test('countCharacters', () => { -// expect(mod.countCharacters('Hello world and moon')).toBe(20) -// }) - -// test('countLines', () => { -// expect(mod.countLines('Hello world and moon')).toBe(1) -// expect(mod.countLines('Hello world and moon\nHello world and moon')).toBe(2) -// }) diff --git a/src/modifiers.ts b/src/modifiers.ts index c9abeff7..69821107 100644 --- a/src/modifiers.ts +++ b/src/modifiers.ts @@ -50,12 +50,12 @@ export function surroundWith(text: string, start: string, end: string): string { * Adds plurals to a string except for excluded words. * @info This handles most english pluralisation rules, but there are exceptions. */ -export function pluralize(value: string, count: number): string { - if (count === 1 || !value || typeof value !== 'string') return value || ''; +export function pluralize(word: string, count: number): string { + if (count === 1 || !word || typeof word !== 'string') return word - value = value.trim().toLowerCase(); - if (unchangingPlurals.has(value)) return value; - if (irregularPlurals.has(value)) return irregularPlurals.get(value) || value; + word = word.trim().toLowerCase() + if (unchangingPlurals.has(word)) return word + if (irregularPlurals.has(word)) return irregularPlurals.get(word)! const suffixRules = new Map([ ['ch', 'ches'], @@ -72,12 +72,12 @@ export function pluralize(value: string, count: number): string { ]); for (const [suffix, replacement] of suffixRules) { - if (value.endsWith(suffix)) { - return value.slice(0, -suffix.length) + replacement; + if (word.endsWith(suffix)) { + return word.slice(0, -suffix.length) + replacement; } } - return value + 's'; + return word + 's'; } /** @@ -199,8 +199,10 @@ export function deslugify(text: string): string { * Removes spaces and capitalizes the first letter of each word except for the first word. */ export function camelCase(text: string): string { + if (!text) return '' return text .trim() + .replace(/[^\w\s-]/g, '') .split(/[-\s]/) .map((word, index) => { if (index === 0) return word.toLowerCase() @@ -213,8 +215,10 @@ export function camelCase(text: string): string { * Removes spaces and capitalizes the first letter of each word. */ export function pascalCase(text: string): string { + if (!text) return '' return text .trim() + .replace(/[^\w\s-]/g, '') .split(/[-\s]/) .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) .join('') @@ -224,18 +228,24 @@ export function pascalCase(text: string): string { * Replaces spaces with underscores and converts to lowercase. */ export function snakeCase(text: string): string { - return text.trim().replace(/\s+/g, '_').toLowerCase() + if (!text) return '' + return text + .trim() + .replace(/[^\w\s]/g, '') + .replace(/\s+/g, '_') + .toLowerCase() } /** * Replaces spaces with hyphens and converts to lowercase. */ export function kebabCase(text: string): string { + if (!text) return '' return text - .replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => { - return index === 0 ? word.toLowerCase() : '-' + word.toLowerCase() - }) - .replace(/\s+/g, '') + .trim() + .toLowerCase() + .replace(/[^\w\s-]/g, '') + .replace(/\s+/g, '-') } /** diff --git a/src/validators.ts b/src/validators.ts index 242cac42..33a16017 100644 --- a/src/validators.ts +++ b/src/validators.ts @@ -304,109 +304,109 @@ export function isMacAddress(value: string): boolean { return regex.test(value); } -/** - * Check if you're a passionate iPhone fan. - */ -export function isIos(): boolean { - return /iPad|iPhone|iPod/.test(navigator.platform); -} - -/** - * Check if you're a fervent Windows fan. - */ -export function isWindows(): boolean { - return /Win/.test(navigator.platform); -} - -/** - * Check if you're a devoted Linux fan. - */ -export function isLinux(): boolean { - return /Linux/.test(navigator.platform); -} - -/** - * Check if you're a zealous Android fan. - */ -export function isAndroid(): boolean { - return /Android/.test(navigator.platform); -} - -/** - * Check if you're a staunch Mac fan. - */ -export function isMac(): boolean { - return /Mac/.test(navigator.platform); -} - -/** - * Check if you're a die-hard Chrome fan. - */ -export function isChrome(): boolean { - return /Chrome/.test(navigator.userAgent); -} - -/** - * Check if you're a dedicated Firefox fan. - */ -export function isFirefox(): boolean { - return /Firefox/.test(navigator.userAgent); -} - -/** - * Check if you're a lonely Safari fan. - */ -export function isSafari(): boolean { - return /Safari/.test(navigator.userAgent); -} - -/** - * Check if you're an ardent Edge fan. - */ -export function isEdge(): boolean { - return /Edge/.test(navigator.userAgent); -} - -/** - * Check if you're rocking a mobile - */ -export function isMobile(): boolean { - return /Mobi/.test(navigator.userAgent); -} - -/** - * Check if you're tablet user - */ -export function isTablet(): boolean { - return /Tablet/.test(navigator.userAgent); -} - -/** - * Check if you're pro desktop user - */ -export function isDesktop(): boolean { - return !isMobile() && !isTablet(); -} - -/** - * Check if you're portrait - */ -export function isPortrait(): boolean { - return window.innerHeight > window.innerWidth; -} - -/** - * Check if you're landscape - */ -export function isLandscape(): boolean { - return window.innerWidth > window.innerHeight; -} - -/** - * Check if you're a cyborg or a bot - */ -export function isBot(): boolean { - return /bot|googlebot|crawler|spider|robot|crawling/i.test( - navigator.userAgent - ); -} +// /** +// * Check if you're a passionate iPhone fan. +// */ +// export function isIos(): boolean { +// return /iPad|iPhone|iPod/.test(navigator.platform); +// } + +// /** +// * Check if you're a fervent Windows fan. +// */ +// export function isWindows(): boolean { +// return /Win/.test(navigator.platform); +// } + +// /** +// * Check if you're a devoted Linux fan. +// */ +// export function isLinux(): boolean { +// return /Linux/.test(navigator.platform); +// } + +// /** +// * Check if you're a zealous Android fan. +// */ +// export function isAndroid(): boolean { +// return /Android/.test(navigator.platform); +// } + +// /** +// * Check if you're a staunch Mac fan. +// */ +// export function isMac(): boolean { +// return /Mac/.test(navigator.platform); +// } + +// /** +// * Check if you're a die-hard Chrome fan. +// */ +// export function isChrome(): boolean { +// return /Chrome/.test(navigator.userAgent); +// } + +// /** +// * Check if you're a dedicated Firefox fan. +// */ +// export function isFirefox(): boolean { +// return /Firefox/.test(navigator.userAgent); +// } + +// /** +// * Check if you're a lonely Safari fan. +// */ +// export function isSafari(): boolean { +// return /Safari/.test(navigator.userAgent); +// } + +// /** +// * Check if you're an ardent Edge fan. +// */ +// export function isEdge(): boolean { +// return /Edge/.test(navigator.userAgent); +// } + +// /** +// * Check if you're rocking a mobile +// */ +// export function isMobile(): boolean { +// return /Mobi/.test(navigator.userAgent); +// } + +// /** +// * Check if you're tablet user +// */ +// export function isTablet(): boolean { +// return /Tablet/.test(navigator.userAgent); +// } + +// /** +// * Check if you're pro desktop user +// */ +// export function isDesktop(): boolean { +// return !isMobile() && !isTablet(); +// } + +// /** +// * Check if you're portrait +// */ +// export function isPortrait(): boolean { +// return window.innerHeight > window.innerWidth; +// } + +// /** +// * Check if you're landscape +// */ +// export function isLandscape(): boolean { +// return window.innerWidth > window.innerHeight; +// } + +// /** +// * Check if you're a cyborg or a bot +// */ +// export function isBot(): boolean { +// return /bot|googlebot|crawler|spider|robot|crawling/i.test( +// navigator.userAgent +// ); +// } From c8e4dfdfb18d788563b092fa4a8c0d149b9d9ffb Mon Sep 17 00:00:00 2001 From: Jeremy Butler Date: Wed, 22 May 2024 21:41:53 +1000 Subject: [PATCH 24/41] Refactor validators and add additional tests --- src/validators.test.ts | 13 ++++++++++++- src/validators.ts | 2 -- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/validators.test.ts b/src/validators.test.ts index 307965ff..85949a83 100644 --- a/src/validators.test.ts +++ b/src/validators.test.ts @@ -173,20 +173,31 @@ test("isPrime", () => { test("isInteger", () => { expect(mod.isInteger(2)).toBe(true); + expect(mod.isInteger(-2)).toBe(true); expect(mod.isInteger(2.5)).toBe(false); expect(mod.isInteger("hello")).toBe(false); expect(mod.isInteger("2")).toBe(false); expect(mod.isInteger({})).toBe(false); + expect(mod.isInteger([])).toBe(false); + expect(mod.isInteger(null)).toBe(false); + expect(mod.isInteger(true)).toBe(false); + expect(mod.isInteger(undefined)).toBe(false); }); test("isFloat", () => { expect(mod.isFloat(2.5)).toBe(true); - expect(mod.isFloat({})).toBe(false); expect(mod.isFloat(2)).toBe(false); + expect(mod.isFloat("hello")).toBe(false); + expect(mod.isFloat({})).toBe(false); + expect(mod.isFloat([])).toBe(false); + expect(mod.isFloat(null)).toBe(false); + expect(mod.isFloat(true)).toBe(false); + expect(mod.isFloat(undefined)).toBe(false); }); test("isBetween", () => { expect(mod.isBetween(4, 2, 6)).toBe(true); + expect(mod.isBetween(4, 6, 2)).toBe(true); expect(mod.isBetween(4, 6, 8)).toBe(false); }); diff --git a/src/validators.ts b/src/validators.ts index 33a16017..0ba5b32f 100644 --- a/src/validators.ts +++ b/src/validators.ts @@ -214,7 +214,6 @@ export function isPrime(value: number): boolean { */ export function isInteger(value: any): boolean { if (typeof value !== "number") return false; - if (!isNumber(value)) return false; return (value) % 1 === 0; } @@ -223,7 +222,6 @@ export function isInteger(value: any): boolean { */ export function isFloat(value: any): boolean { if (typeof value !== "number") return false; - if (!isNumber(value)) return false; return !isInteger(value); } From b3f8fe1d586c3bcd111b184eafea5c179dc3a7ec Mon Sep 17 00:00:00 2001 From: Jeremy Butler Date: Thu, 23 May 2024 08:26:32 +1000 Subject: [PATCH 25/41] Refactor dataSortBy and dataReverse functions --- src/data.test.ts | 92 ++++++++++++++++++++++++++++-------------------- src/data.ts | 33 +++++++++-------- 2 files changed, 73 insertions(+), 52 deletions(-) diff --git a/src/data.test.ts b/src/data.test.ts index 82a60c21..906646c8 100644 --- a/src/data.test.ts +++ b/src/data.test.ts @@ -1,65 +1,81 @@ -import { expect, test } from 'vitest' -import * as mod from './data' +import { expect, test } from 'vitest'; +import * as mod from './data'; -const arrayExample = [ - { name: 'John', age: 25 }, - { name: 'Jane', age: 30 }, - { name: 'Jake', age: 20 } -] +const arrayExample = [{ name: 'John', age: 25 },{ name: 'Jane', age: 30 },{ name: 'Jake', age: 20 }]; -const objectExample = { +const objectExample: { name: string; age: number; country: string } = { name: 'John', age: 25, country: 'USA' -} +}; -// test('dataShuffle', () => { -// expect(mod.dataShuffle(items)).not.toStrictEqual(items) -// }) +const nestedArray = [1, [2, 3], [4, [5, 6]]]; +const duplicateArray = [1, 2, 2, 3, 4, 5, 5, 5]; +const stringArray = ['apple', 'banana', 'apple', 'cherry', 'banana']; +const mixedArray = [1, 2, 3, 4, 5]; -test('dataReverse', () => { - expect(mod.dataReverse(arrayExample)).toStrictEqual([ - { name: 'Jake', age: 20 }, +test('dataSortBy', () => { + // Empty Property + expect(mod.dataSortBy(arrayExample)).toStrictEqual([ + { name: 'John', age: 25 }, { name: 'Jane', age: 30 }, - { name: 'John', age: 25 } - ]) - expect(mod.dataReverse(objectExample)).toStrictEqual({ - country: 'USA', - age: 25, - name: 'John', - }) -}) - -test('dataRemoveDuplicates', () => { - expect(mod.dataRemoveDuplicates([1, 2, 2, 3, 4, 5, 5, 5])).toStrictEqual([1, 2, 3, 4, 5]) - expect(mod.dataRemoveDuplicates(['apple', 'banana', 'apple', 'cherry', 'banana'])).toStrictEqual(['apple', 'banana', 'cherry']) -}) + { name: 'Jake', age: 20 } + ]); -test('dataSortBy', () => { + // Sort by age expect(mod.dataSortBy(arrayExample, { property: 'age' })).toStrictEqual([ { name: 'Jake', age: 20 }, { name: 'John', age: 25 }, { name: 'Jane', age: 30 } - ]) + ]); + // Sort by age desc expect(mod.dataSortBy(arrayExample, { property: 'age', order: 'desc' })).toStrictEqual([ { name: 'Jane', age: 30 }, { name: 'John', age: 25 }, { name: 'Jake', age: 20 } - ]) + ]); expect(mod.dataSortBy(arrayExample, { property: 'name' })).toStrictEqual([ { name: 'Jake', age: 20 }, { name: 'Jane', age: 30 }, { name: 'John', age: 25 } - ]) -}) + ]); +}); + +test('dataReverse', () => { + const arrayExample = [{ name: 'John', age: 25 },{ name: 'Jane', age: 30 },{ name: 'Jake', age: 20 }]; + expect(mod.dataReverse(arrayExample)).toStrictEqual([ + { name: 'Jake', age: 20 }, + { name: 'Jane', age: 30 }, + { name: 'John', age: 25 } + ]); + expect(mod.dataReverse(objectExample)).toMatchObject({ + country: 'USA', + age: 25, + name: 'John', + }); + + // Empty Items + expect(mod.dataReverse([])).toStrictEqual([]); +}); + +test('dataRemoveDuplicates', () => { + expect(mod.dataRemoveDuplicates(duplicateArray)).toStrictEqual([1, 2, 3, 4, 5]); + expect(mod.dataRemoveDuplicates(stringArray)).toStrictEqual(['apple', 'banana', 'cherry']); +}); test('dataFlatten', () => { - expect(mod.dataFlatten([1, [2, 3], [4, [5, 6]]])).toStrictEqual([1, 2, 3, 4, 5, 6]) -}) + expect(mod.dataFlatten(nestedArray)).toStrictEqual([1, 2, 3, 4, 5, 6]); + + // Objects + expect(mod.dataFlatten({ a: { b: 1 }, c: { d: 2 } })).toStrictEqual({ b: 1, d: 2 }); + + // Empty Items + expect(mod.dataFlatten([])).toStrictEqual([]); +}); test('dataWithout', () => { - expect(mod.dataWithout([1, 2, 3, 4, 5], [2, 4])).toStrictEqual([1, 3, 5]) - expect(mod.dataWithout([1, 2, 3, 4, 5], 2)).toStrictEqual([1, 3, 4, 5]) -}) + expect(mod.dataWithout(mixedArray, [2, 4])).toStrictEqual([1, 3, 5]); + expect(mod.dataWithout(mixedArray, 2)).toStrictEqual([1, 3, 4, 5]); +}); \ No newline at end of file diff --git a/src/data.ts b/src/data.ts index a481465b..0fa0b40f 100644 --- a/src/data.ts +++ b/src/data.ts @@ -8,21 +8,26 @@ import { isObject } from './validators' * Sort an array or object by a property. */ export function dataSortBy(items: object | string[] | number[], options?: { property?: string; order?: 'asc' | 'desc' }): object | string[] | number[] { - const comparator = (a: string | number, b: string | number) => { - const property = options?.property - const order = options?.order ?? 'asc' - if (!property) return 0 - - if (a[property] < b[property]) return order === 'asc' ? -1 : 1 - if (a[property] > b[property]) return order === 'asc' ? 1 : -1 - return 0 - } + const { property, order = 'asc' } = options || {}; + if (!property) return items; + + const comparator = (a: any, b: any) => { + const aValue = a[property]; + const bValue = b[property]; + + if (Array.isArray(aValue) && Array.isArray(bValue)) { + return (aValue.length - bValue.length) * (order === 'asc' ? 1 : -1); + } else if (typeof aValue === 'string' && typeof bValue === 'string') { + return aValue.localeCompare(bValue) * (order === 'asc' ? 1 : -1); + } else { + return (aValue - bValue) * (order === 'asc' ? 1 : -1); + } + }; if (isObject(items)) { - const entries = Object.entries(items) - return Object.fromEntries(entries.sort((a, b) => comparator(a[1], b[1])) as [string, string | number | object | string[] | number[]][]) + return Object.fromEntries(Object.entries(items).sort((a, b) => comparator(a[1], b[1]))); } else { - return (items as string[] | number[]).sort(comparator) + return (items as string[] | number[]).sort(comparator); } } @@ -30,9 +35,9 @@ export function dataSortBy(items: object | string[] | number[], options?: { prop * Reverse an array or object. */ export function dataReverse(items: object | string[] | number[]): object | string[] | number[] { - if (!items) { + if ((Array.isArray(items) && items.length === 0) || (!Array.isArray(items) && Object.keys(items).length === 0)) { console.warn('[MODS] Warning: dataReverse() expects an object or array as the first argument.') - return items + return items; } if (isObject(items)) { From 049ed554e61beec2456079e8b8a5d0de4bc0dc72 Mon Sep 17 00:00:00 2001 From: Jeremy Butler Date: Thu, 23 May 2024 10:05:17 +1000 Subject: [PATCH 26/41] Update copy button icon --- nuxt-web/components/example/Code.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nuxt-web/components/example/Code.vue b/nuxt-web/components/example/Code.vue index 3d1efc99..ed99f300 100644 --- a/nuxt-web/components/example/Code.vue +++ b/nuxt-web/components/example/Code.vue @@ -8,7 +8,7 @@ @@ -10,20 +10,27 @@ diff --git a/nuxt-web/components/content/actions/ScrollToAnchor.vue b/nuxt-web/components/content/actions/ScrollToAnchor.vue index 618b9b49..1a33f6f2 100644 --- a/nuxt-web/components/content/actions/ScrollToAnchor.vue +++ b/nuxt-web/components/content/actions/ScrollToAnchor.vue @@ -1,7 +1,7 @@ +.disabled { + @apply pointer-events-none cursor-not-allowed opacity-30; +} + \ No newline at end of file From 92047f008936fd4133af5990c7b882ff499eaced Mon Sep 17 00:00:00 2001 From: Jeremy Butler Date: Fri, 24 May 2024 16:45:48 +1000 Subject: [PATCH 36/41] Fix import statements and indentation --- eslint.config.js | 10 +++++----- src/actions.ts | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 5915c4e8..43cacfce 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,8 +1,8 @@ // eslint.config.js -import globals from 'globals'; -import pluginJs from '@eslint/js'; -import tseslint from 'typescript-eslint'; -import pluginVue from 'eslint-plugin-vue'; +import globals from 'globals' +import pluginJs from '@eslint/js' +import tseslint from 'typescript-eslint' +import pluginVue from 'eslint-plugin-vue' import stylistic from '@stylistic/eslint-plugin' export default [ @@ -13,8 +13,8 @@ export default [ rules: { '@stylistic/indent': ['error', 2], '@stylistic/quotes': ['error', 'single'], + '@stylistic/semi': ['error', 'never'], }, - ignorePatterns: ['node_modules', '.nuxt', 'dist', 'nuxt-web', 'nuxt-module'], }, { languageOptions: { globals: globals.browser } }, pluginJs.configs.recommended, diff --git a/src/actions.ts b/src/actions.ts index 67d48743..e229d830 100644 --- a/src/actions.ts +++ b/src/actions.ts @@ -99,7 +99,7 @@ export async function copyToClipboard(value: string | number): Promise { export function toggleFullScreen(): Promise { return new Promise((resolve, reject) => { if (document.fullscreenElement) { - document.exitFullscreen().then(resolve).catch(reject) + document.exitFullscreen().then(resolve).catch(reject) } else { document.documentElement.requestFullscreen().then(resolve).catch(reject) } From 613f7c602c0abb4c304eb08af2e4cb388f0c0b69 Mon Sep 17 00:00:00 2001 From: Jeremy Butler Date: Fri, 24 May 2024 16:47:57 +1000 Subject: [PATCH 37/41] Fix ESlint formatting --- src/actions.ts | 10 +- src/config.ts | 450 ++++++++++++++++++------------------- src/data.test.ts | 74 +++---- src/data.ts | 2 +- src/formatters.test.ts | 8 +- src/formatters.ts | 76 +++---- src/generators.test.ts | 10 +- src/generators.ts | 22 +- src/modifiers.test.ts | 10 +- src/modifiers.ts | 14 +- src/numbers.ts | 32 +-- src/validators.test.ts | 488 ++++++++++++++++++++--------------------- src/validators.ts | 130 +++++------ 13 files changed, 663 insertions(+), 663 deletions(-) diff --git a/src/actions.ts b/src/actions.ts index e229d830..cb897941 100644 --- a/src/actions.ts +++ b/src/actions.ts @@ -82,14 +82,14 @@ export function toggleElementScroll(element: HTMLElement): Promise { */ export async function copyToClipboard(value: string | number): Promise { if (!navigator.clipboard || !navigator.clipboard.writeText) { - throw new Error('Clipboard API is not available'); + throw new Error('Clipboard API is not available') } try { - await navigator.clipboard.writeText(String(value)); + await navigator.clipboard.writeText(String(value)) } catch (error) { - console.error('Failed to copy text: ', error); - throw error; + console.error('Failed to copy text: ', error) + throw error } } @@ -99,7 +99,7 @@ export async function copyToClipboard(value: string | number): Promise { export function toggleFullScreen(): Promise { return new Promise((resolve, reject) => { if (document.fullscreenElement) { - document.exitFullscreen().then(resolve).catch(reject) + document.exitFullscreen().then(resolve).catch(reject) } else { document.documentElement.requestFullscreen().then(resolve).catch(reject) } diff --git a/src/config.ts b/src/config.ts index 3d66d9af..a89bb8bf 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,244 +1,244 @@ export const currencySymbols = new Map([ - ['en-US', 'USD'], // US Dollar - ['en-GB', 'GBP'], // British Pound - ['en-AU', 'AUD'], // Australian Dollar - ['en-CA', 'CAD'], // Canadian Dollar - ['en-NZ', 'NZD'], // New Zealand Dollar - ['en-ZA', 'ZAR'], // South African Rand - ['de-DE', 'EUR'], // Euro (Germany) - ['fr-FR', 'EUR'], // Euro (France) - ['es-ES', 'EUR'], // Euro (Spain) - ['it-IT', 'EUR'], // Euro (Italy) - ['pt-PT', 'EUR'], // Euro (Portugal) - ['nl-NL', 'EUR'], // Euro (Netherlands) - ['fi-FI', 'EUR'], // Euro (Finland) - ['da-DK', 'DKK'], // Danish Krone - ['sv-SE', 'SEK'], // Swedish Krona - ['nb-NO', 'NOK'], // Norwegian Krone - ['pl-PL', 'PLN'], // Polish Zloty - ['tr-TR', 'TRY'], // Turkish Lira - ['ru-RU', 'RUB'], // Russian Ruble - ['ja-JP', 'JPY'], // Japanese Yen - ['zh-CN', 'CNY'], // Chinese Yuan - ['ko-KR', 'KRW'], // South Korean Won - ['ar-SA', 'SAR'], // Saudi Riyal - ['he-IL', 'ILS'], // Israeli Shekel - ['id-ID', 'IDR'], // Indonesian Rupiah - ['ms-MY', 'MYR'], // Malaysian Ringgit - ['th-TH', 'THB'], // Thai Baht - ['vi-VN', 'VND'], // Vietnamese Dong - ['hi-IN', 'INR'], // Indian Rupee - ['bn-IN', 'INR'], // Indian Rupee - ['pa-IN', 'INR'], // Indian Rupee - ['gu-IN', 'INR'], // Indian Rupee - ['or-IN', 'INR'], // Indian Rupee - ['ta-IN', 'INR'], // Indian Rupee - ['te-IN', 'INR'], // Indian Rupee - ['kn-IN', 'INR'], // Indian Rupee - ['ml-IN', 'INR'] // Indian Rupee + ['en-US', 'USD'], // US Dollar + ['en-GB', 'GBP'], // British Pound + ['en-AU', 'AUD'], // Australian Dollar + ['en-CA', 'CAD'], // Canadian Dollar + ['en-NZ', 'NZD'], // New Zealand Dollar + ['en-ZA', 'ZAR'], // South African Rand + ['de-DE', 'EUR'], // Euro (Germany) + ['fr-FR', 'EUR'], // Euro (France) + ['es-ES', 'EUR'], // Euro (Spain) + ['it-IT', 'EUR'], // Euro (Italy) + ['pt-PT', 'EUR'], // Euro (Portugal) + ['nl-NL', 'EUR'], // Euro (Netherlands) + ['fi-FI', 'EUR'], // Euro (Finland) + ['da-DK', 'DKK'], // Danish Krone + ['sv-SE', 'SEK'], // Swedish Krona + ['nb-NO', 'NOK'], // Norwegian Krone + ['pl-PL', 'PLN'], // Polish Zloty + ['tr-TR', 'TRY'], // Turkish Lira + ['ru-RU', 'RUB'], // Russian Ruble + ['ja-JP', 'JPY'], // Japanese Yen + ['zh-CN', 'CNY'], // Chinese Yuan + ['ko-KR', 'KRW'], // South Korean Won + ['ar-SA', 'SAR'], // Saudi Riyal + ['he-IL', 'ILS'], // Israeli Shekel + ['id-ID', 'IDR'], // Indonesian Rupiah + ['ms-MY', 'MYR'], // Malaysian Ringgit + ['th-TH', 'THB'], // Thai Baht + ['vi-VN', 'VND'], // Vietnamese Dong + ['hi-IN', 'INR'], // Indian Rupee + ['bn-IN', 'INR'], // Indian Rupee + ['pa-IN', 'INR'], // Indian Rupee + ['gu-IN', 'INR'], // Indian Rupee + ['or-IN', 'INR'], // Indian Rupee + ['ta-IN', 'INR'], // Indian Rupee + ['te-IN', 'INR'], // Indian Rupee + ['kn-IN', 'INR'], // Indian Rupee + ['ml-IN', 'INR'] // Indian Rupee ]) export const configLocales = new Set([ - 'en-US', // English (United States) - 'en-GB', // English (United Kingdom) - 'en-CA', // English (Canada) - 'en-AU', // English (Australia) - 'fr-FR', // French (France) - 'es-ES', // Spanish (Spain) - 'de-DE', // German (Germany) - 'it-IT', // Italian (Italy) - 'ja-JP', // Japanese (Japan) - 'ko-KR', // Korean (South Korea) - 'zh-CN', // Chinese (Simplified, China) - 'zh-TW', // Chinese (Traditional, Taiwan) - 'pt-PT', // Portuguese (Portugal) - 'pt-BR', // Portuguese (Brazil) - 'ru-RU', // Russian (Russia) - 'nl-NL', // Dutch (Netherlands) - 'da-DK', // Danish (Denmark) - 'sv-SE', // Swedish (Sweden) - 'nb-NO', // Norwegian BokmÃ¥l (Norway) - 'fi-FI', // Finnish (Finland) - 'pl-PL', // Polish (Poland) - 'tr-TR', // Turkish (Turkey) - 'ar-SA', // Arabic (Saudi Arabia) - 'he-IL', // Hebrew (Yiddish) - 'id-ID', // Indonesian (Indonesia) - 'ms-MY', // Malay (Malaysia) - 'th-TH', // Thai (Thailand) - 'vi-VN' // Vietnamese (Vietnam) + 'en-US', // English (United States) + 'en-GB', // English (United Kingdom) + 'en-CA', // English (Canada) + 'en-AU', // English (Australia) + 'fr-FR', // French (France) + 'es-ES', // Spanish (Spain) + 'de-DE', // German (Germany) + 'it-IT', // Italian (Italy) + 'ja-JP', // Japanese (Japan) + 'ko-KR', // Korean (South Korea) + 'zh-CN', // Chinese (Simplified, China) + 'zh-TW', // Chinese (Traditional, Taiwan) + 'pt-PT', // Portuguese (Portugal) + 'pt-BR', // Portuguese (Brazil) + 'ru-RU', // Russian (Russia) + 'nl-NL', // Dutch (Netherlands) + 'da-DK', // Danish (Denmark) + 'sv-SE', // Swedish (Sweden) + 'nb-NO', // Norwegian BokmÃ¥l (Norway) + 'fi-FI', // Finnish (Finland) + 'pl-PL', // Polish (Poland) + 'tr-TR', // Turkish (Turkey) + 'ar-SA', // Arabic (Saudi Arabia) + 'he-IL', // Hebrew (Yiddish) + 'id-ID', // Indonesian (Indonesia) + 'ms-MY', // Malay (Malaysia) + 'th-TH', // Thai (Thailand) + 'vi-VN' // Vietnamese (Vietnam) ]) export const configUnits = new Set([ - 'acre', - 'bit', - 'byte', - 'celsius', - 'centimeter', - 'day', - 'degree', - 'fahrenheit', - 'fluid-ounce', - 'foot', - 'gallon', - 'gigabit', - 'gigabyte', - 'gram', - 'hectare', - 'hour', - 'inch', - 'kilobit', - 'kilobyte', - 'kilogram', - 'kilometer', - 'liter', - 'megabit', - 'megabyte', - 'meter', - 'microsecond', - 'mile', - 'mile-scandinavian', - 'milliliter', - 'millimeter', - 'millisecond', - 'minute', - 'month', - 'nanosecond', - 'ounce', - 'percent', - 'petabyte', - 'pound', - 'second', - 'stone', - 'terabit', - 'terabyte', - 'week', - 'yard', - 'year' + 'acre', + 'bit', + 'byte', + 'celsius', + 'centimeter', + 'day', + 'degree', + 'fahrenheit', + 'fluid-ounce', + 'foot', + 'gallon', + 'gigabit', + 'gigabyte', + 'gram', + 'hectare', + 'hour', + 'inch', + 'kilobit', + 'kilobyte', + 'kilogram', + 'kilometer', + 'liter', + 'megabit', + 'megabyte', + 'meter', + 'microsecond', + 'mile', + 'mile-scandinavian', + 'milliliter', + 'millimeter', + 'millisecond', + 'minute', + 'month', + 'nanosecond', + 'ounce', + 'percent', + 'petabyte', + 'pound', + 'second', + 'stone', + 'terabit', + 'terabyte', + 'week', + 'yard', + 'year' ]) export const unchangingPlurals = new Set([ - 'sheep', - 'fish', - 'deer', - 'hay', - 'moose', - 'series', - 'species', - 'aircraft', - 'bison', - 'buffalo', - 'cod', - 'elk', - 'halibut', - 'hovercraft', - 'lego', - 'mackerel', - 'salmon', - 'spacecraft', - 'swine', - 'trout', - 'tuna' -]); + 'sheep', + 'fish', + 'deer', + 'hay', + 'moose', + 'series', + 'species', + 'aircraft', + 'bison', + 'buffalo', + 'cod', + 'elk', + 'halibut', + 'hovercraft', + 'lego', + 'mackerel', + 'salmon', + 'spacecraft', + 'swine', + 'trout', + 'tuna' +]) export const irregularPlurals = new Map([ - ['addendum', 'addenda'], - ['agendum', 'agenda'], - ['alumnus', 'alumni'], - ['analysis', 'analyses'], - ['anathema', 'anathemata'], - ['appendix', 'appendices'], - ['axis', 'axes'], - ['bacterium', 'bacteria'], - ['basis', 'bases'], - ['cactus', 'cacti'], - ['cherub', 'cherubim'], - ['child', 'children'], - ['corrigendum', 'corrigenda'], - ['crisis', 'crises'], - ['criterion', 'criteria'], - ['curriculum', 'curricula'], - ['custom', 'customs'], - ['datum', 'data'], - ['diagnosis', 'diagnoses'], - ['dogma', 'dogmata'], - ['ellipsis', 'ellipses'], - ['elf', 'elves'], - ['erratum', 'errata'], - ['focus', 'foci'], - ['foot', 'feet'], - ['forum', 'fora'], - ['fungus', 'fungi'], - ['genus', 'genera'], - ['goose', 'geese'], - ['half', 'halves'], - ['hypothesis', 'hypotheses'], - ['index', 'indices'], - ['knife', 'knives'], - ['leaf', 'leaves'], - ['lemma', 'lemmata'], - ['life', 'lives'], - ['loaf', 'loaves'], - ['man', 'men'], - ['matrix', 'matrices'], - ['medium', 'media'], - ['memorandum', 'memoranda'], - ['millennium', 'millennia'], - ['mouse', 'mice'], - ['nucleus', 'nuclei'], - ['oasis', 'oases'], - ['ovum', 'ova'], - ['ox', 'oxen'], - ['parenthesis', 'parentheses'], - ['person', 'people'], - ['phenomenon', 'phenomena'], - ['potato', 'potatoes'], - ['radius', 'radii'], - ['schema', 'schemata'], - ['stimulus', 'stimuli'], - ['stigma', 'stigmata'], - ['stoma', 'stomata'], - ['stratum', 'strata'], - ['syllabus', 'syllabi'], - ['symposium', 'symposia'], - ['synthesis', 'syntheses'], - ['thesis', 'theses'], - ['tooth', 'teeth'], - ['tomato', 'tomatoes'], - ['vertex', 'vertices'], - ['wife', 'wives'], - ['woman', 'women'] -]); + ['addendum', 'addenda'], + ['agendum', 'agenda'], + ['alumnus', 'alumni'], + ['analysis', 'analyses'], + ['anathema', 'anathemata'], + ['appendix', 'appendices'], + ['axis', 'axes'], + ['bacterium', 'bacteria'], + ['basis', 'bases'], + ['cactus', 'cacti'], + ['cherub', 'cherubim'], + ['child', 'children'], + ['corrigendum', 'corrigenda'], + ['crisis', 'crises'], + ['criterion', 'criteria'], + ['curriculum', 'curricula'], + ['custom', 'customs'], + ['datum', 'data'], + ['diagnosis', 'diagnoses'], + ['dogma', 'dogmata'], + ['ellipsis', 'ellipses'], + ['elf', 'elves'], + ['erratum', 'errata'], + ['focus', 'foci'], + ['foot', 'feet'], + ['forum', 'fora'], + ['fungus', 'fungi'], + ['genus', 'genera'], + ['goose', 'geese'], + ['half', 'halves'], + ['hypothesis', 'hypotheses'], + ['index', 'indices'], + ['knife', 'knives'], + ['leaf', 'leaves'], + ['lemma', 'lemmata'], + ['life', 'lives'], + ['loaf', 'loaves'], + ['man', 'men'], + ['matrix', 'matrices'], + ['medium', 'media'], + ['memorandum', 'memoranda'], + ['millennium', 'millennia'], + ['mouse', 'mice'], + ['nucleus', 'nuclei'], + ['oasis', 'oases'], + ['ovum', 'ova'], + ['ox', 'oxen'], + ['parenthesis', 'parentheses'], + ['person', 'people'], + ['phenomenon', 'phenomena'], + ['potato', 'potatoes'], + ['radius', 'radii'], + ['schema', 'schemata'], + ['stimulus', 'stimuli'], + ['stigma', 'stigmata'], + ['stoma', 'stomata'], + ['stratum', 'strata'], + ['syllabus', 'syllabi'], + ['symposium', 'symposia'], + ['synthesis', 'syntheses'], + ['thesis', 'theses'], + ['tooth', 'teeth'], + ['tomato', 'tomatoes'], + ['vertex', 'vertices'], + ['wife', 'wives'], + ['woman', 'women'] +]) export const numberUnderTwenty: string[] = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen'] export const numberTens: string[] = ['twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety'] export const numberScales: string[] = ['', ' thousand', ' million', ' billion', ' trillion', ' quadrillion', ' quintillion'] export const formatTitleExceptions = new Set([ - 'a', - 'an', - 'to', - 'the', - 'for', - 'and', - 'nor', - 'but', - 'or', - 'yet', - 'so', - 'in', - 'is', - 'it', - 'than', - 'on', - 'at', - 'with', - 'under', - 'above', - 'from', - 'of', - 'although', - 'because', - 'since', - 'unless' + 'a', + 'an', + 'to', + 'the', + 'for', + 'and', + 'nor', + 'but', + 'or', + 'yet', + 'so', + 'in', + 'is', + 'it', + 'than', + 'on', + 'at', + 'with', + 'under', + 'above', + 'from', + 'of', + 'although', + 'because', + 'since', + 'unless' ]) \ No newline at end of file diff --git a/src/data.test.ts b/src/data.test.ts index 6736c223..593942c9 100644 --- a/src/data.test.ts +++ b/src/data.test.ts @@ -1,52 +1,52 @@ -import { expect, test } from 'vitest'; -import * as mod from './data'; +import { expect, test } from 'vitest' +import * as mod from './data' -const arrayExample = [{ name: 'John', age: 25 }, { name: 'Jane', age: 30 }, { name: 'Jimmy', age: 2 }, { name: 'Jake', age: 20 }]; +const arrayExample = [{ name: 'John', age: 25 }, { name: 'Jane', age: 30 }, { name: 'Jimmy', age: 2 }, { name: 'Jake', age: 20 }] const objectExample: { name: string; age: number; country: string } = { name: 'John', age: 25, country: 'USA' -}; +} -const nestedArray = [1, [2, 3], [4, [5, 6]]]; -const duplicateArray = [1, 2, 2, 3, 4, 5, 5, 5]; -const stringArray = ['apple', 'banana', 'apple', 'cherry', 'banana']; -const mixedArray = [1, 2, 3, 4, 5]; +const nestedArray = [1, [2, 3], [4, [5, 6]]] +const duplicateArray = [1, 2, 2, 3, 4, 5, 5, 5] +const stringArray = ['apple', 'banana', 'apple', 'cherry', 'banana'] +const mixedArray = [1, 2, 3, 4, 5] test('dataSortBy', () => { // Empty expect(mod.dataSortBy(arrayExample)).toStrictEqual([ { name: 'John', age: 25 }, { name: 'Jane', age: 30 }, { name: 'Jimmy', age: 2 }, { name: 'Jake', age: 20 } - ]); + ]) // Sort by age (asc) expect(mod.dataSortBy(arrayExample, { property: 'age', order: 'asc' })).toStrictEqual([ { name: 'Jimmy', age: 2 }, { name: 'Jake', age: 20 }, { name: 'John', age: 25 }, { name: 'Jane', age: 30 } - ]); + ]) // Sort by age (desc) expect(mod.dataSortBy(arrayExample, { property: 'age', order: 'desc' })).toStrictEqual([ { name: 'Jane', age: 30 }, { name: 'John', age: 25 }, { name: 'Jake', age: 20 }, { name: 'Jimmy', age: 2 } - ]); + ]) // Sort by name (asc) expect(mod.dataSortBy(arrayExample, { property: 'name', order: 'asc' })).toStrictEqual([ { name: 'Jake', age: 20 }, { name: 'Jane', age: 30 }, { name: 'Jimmy', age: 2 }, { name: 'John', age: 25 } - ]); + ]) // Return Object as-is expect(mod.dataSortBy(objectExample)).toMatchObject({ name: 'John', age: 25, country: 'USA' - }); + }) // Sort number array (asc) - expect(mod.dataSortBy([3, 1, 4, 1, 5, 9], { order: 'asc' })).toEqual([1, 1, 3, 4, 5, 9]); + expect(mod.dataSortBy([3, 1, 4, 1, 5, 9], { order: 'asc' })).toEqual([1, 1, 3, 4, 5, 9]) // Sort number array (desc) - expect(mod.dataSortBy([3, 1, 4, 1, 5, 9], { order: 'desc' })).toEqual([9, 5, 4, 3, 1, 1]); + expect(mod.dataSortBy([3, 1, 4, 1, 5, 9], { order: 'desc' })).toEqual([9, 5, 4, 3, 1, 1]) // Sort by name (asc) expect(mod.dataSortBy( @@ -60,7 +60,7 @@ test('dataSortBy', () => { { name: 'Alice', age: 30 }, { name: 'Bob', age: 20 }, { name: 'Charlie', age: 25 } - ]); + ]) // Sort by name (desc) expect(mod.dataSortBy( @@ -74,57 +74,57 @@ test('dataSortBy', () => { { name: 'Charlie', age: 25 }, { name: 'Bob', age: 20 }, { name: 'Alice', age: 30 } - ]); -}); + ]) +}) test('dataReverse', () => { - const arrayExample = [{ name: 'John', age: 25 }, { name: 'Jane', age: 30 }, { name: 'Jake', age: 20 }]; + const arrayExample = [{ name: 'John', age: 25 }, { name: 'Jane', age: 30 }, { name: 'Jake', age: 20 }] expect(mod.dataReverse(arrayExample)).toStrictEqual([ { name: 'Jake', age: 20 }, { name: 'Jane', age: 30 }, { name: 'John', age: 25 } - ]); + ]) expect(mod.dataReverse(objectExample)).toMatchObject({ country: 'USA', age: 25, name: 'John', - }); + }) // Empty Items - expect(mod.dataReverse([])).toStrictEqual([]); -}); + expect(mod.dataReverse([])).toStrictEqual([]) +}) test('dataRemoveDuplicates', () => { - expect(mod.dataRemoveDuplicates(duplicateArray)).toStrictEqual([1, 2, 3, 4, 5]); - expect(mod.dataRemoveDuplicates(stringArray)).toStrictEqual(['apple', 'banana', 'cherry']); -}); + expect(mod.dataRemoveDuplicates(duplicateArray)).toStrictEqual([1, 2, 3, 4, 5]) + expect(mod.dataRemoveDuplicates(stringArray)).toStrictEqual(['apple', 'banana', 'cherry']) +}) test('dataFlatten', () => { // Nested Array - expect(mod.dataFlatten(nestedArray)).toStrictEqual([1, 2, 3, 4, 5, 6]); + expect(mod.dataFlatten(nestedArray)).toStrictEqual([1, 2, 3, 4, 5, 6]) // Nested Object - expect(mod.dataFlatten({ a: 1, b: { c: 2, d: { e: 3 } } })).toStrictEqual({ a: 1, 'b.c': 2, 'b.d.e': 3 }); + expect(mod.dataFlatten({ a: 1, b: { c: 2, d: { e: 3 } } })).toStrictEqual({ a: 1, 'b.c': 2, 'b.d.e': 3 }) // Empty Items - expect(mod.dataFlatten([])).toStrictEqual([]); + expect(mod.dataFlatten([])).toStrictEqual([]) // Return Object as-is // @ts-expect-error - Testing invalid input - expect(mod.dataFlatten('not-object')).toMatchObject('not-object'); -}); + expect(mod.dataFlatten('not-object')).toMatchObject('not-object') +}) test('dataWithout', () => { // Arrays - expect(mod.dataWithout(mixedArray, [2, 4])).toStrictEqual([1, 3, 5]); - expect(mod.dataWithout(mixedArray, 2)).toStrictEqual([1, 3, 4, 5]); + expect(mod.dataWithout(mixedArray, [2, 4])).toStrictEqual([1, 3, 5]) + expect(mod.dataWithout(mixedArray, 2)).toStrictEqual([1, 3, 4, 5]) // Objects - expect(mod.dataWithout(objectExample, ['name', 'country'])).toStrictEqual({ age: 25 }); - expect(mod.dataWithout(objectExample, 'age')).toStrictEqual({ name: 'John', country: 'USA' }); + expect(mod.dataWithout(objectExample, ['name', 'country'])).toStrictEqual({ age: 25 }) + expect(mod.dataWithout(objectExample, 'age')).toStrictEqual({ name: 'John', country: 'USA' }) // Empty Items - expect(mod.dataWithout([], [])).toStrictEqual([]); -}); + expect(mod.dataWithout([], [])).toStrictEqual([]) +}) diff --git a/src/data.ts b/src/data.ts index 643d0376..5976d3ff 100644 --- a/src/data.ts +++ b/src/data.ts @@ -47,7 +47,7 @@ export function dataSortBy(items: object | string[] | number[], options?: { prop export function dataReverse(items: object | string[] | number[]): object | string[] | number[] { if ((Array.isArray(items) && items.length === 0) || (!Array.isArray(items) && Object.keys(items).length === 0)) { console.warn('[MODS] Warning: dataReverse() expects an object or array as the first argument.') - return items; + return items } if (isObject(items)) { diff --git a/src/formatters.test.ts b/src/formatters.test.ts index dcbec739..623397c3 100644 --- a/src/formatters.test.ts +++ b/src/formatters.test.ts @@ -157,8 +157,8 @@ test('formatTextWrap', () => { expect(mod.formatTextWrap('')).toBe('') expect(mod.formatTextWrap('hello world')).toBe('hello world') expect(mod.formatTextWrap('hello world how are you')).toBe('hello world how are you') - expect(mod.formatTextWrap('This is a test')).toBe('This is a test'); - expect(mod.formatTextWrap('Test')).toBe('Test'); - expect(mod.formatTextWrap('TestTest')).toBe('TestTest'); - expect(mod.formatTextWrap('')).toBe(''); + expect(mod.formatTextWrap('This is a test')).toBe('This is a test') + expect(mod.formatTextWrap('Test')).toBe('Test') + expect(mod.formatTextWrap('TestTest')).toBe('TestTest') + expect(mod.formatTextWrap('')).toBe('') }) diff --git a/src/formatters.ts b/src/formatters.ts index 75418483..fc003551 100644 --- a/src/formatters.ts +++ b/src/formatters.ts @@ -8,16 +8,16 @@ import { currencySymbols, numberUnderTwenty, numberTens, numberScales, formatTit * Format numbers into neat and formatted strings for people */ export function formatNumber(number: number, options?: { decimals?: number; locale?: string }): string { - const decimalPlaces = (number.toString().split('.')[1] || '').length; - const safeDecimals = Math.min(options?.decimals ?? decimalPlaces, decimalPlaces); + const decimalPlaces = (number.toString().split('.')[1] || '').length + const safeDecimals = Math.min(options?.decimals ?? decimalPlaces, decimalPlaces) const config: Intl.NumberFormatOptions = { style: 'decimal', minimumFractionDigits: safeDecimals, maximumFractionDigits: safeDecimals, - }; + } - return new Intl.NumberFormat(options?.locale ?? 'en-US', config).format(number); + return new Intl.NumberFormat(options?.locale ?? 'en-US', config).format(number) } /** @@ -59,8 +59,8 @@ export function formatValuation(number: number, options?: { decimals?: number; l * Format a number into a your unit of choice */ export function formatUnit(number: number, options: { unit: string; decimals?: number; unitDisplay?: 'short' | 'long'; locale?: string }): string { - const decimalPlaces = (number.toString().split('.')[1] || '').length; - const safeDecimals = options?.decimals ?? decimalPlaces; + const decimalPlaces = (number.toString().split('.')[1] || '').length + const safeDecimals = options?.decimals ?? decimalPlaces const config: Intl.NumberFormatOptions = { unit: options.unit, style: 'unit', @@ -90,8 +90,8 @@ export function formatPercentage(number: number, options?: { decimals?: number; * Format time into a human-readable string */ export function formatDurationLabels(seconds: number, options?: { labels?: 'short' | 'long'; round?: boolean }): string { - if (seconds <= 0) return formatUnit(0, { unit: 'second', decimals: 0, unitDisplay: options?.labels ?? 'long' }); - if (options?.round) seconds = Math.round(seconds); + if (seconds <= 0) return formatUnit(0, { unit: 'second', decimals: 0, unitDisplay: options?.labels ?? 'long' }) + if (options?.round) seconds = Math.round(seconds) const units = [ { unit: 'year', value: 31536000 }, @@ -99,68 +99,68 @@ export function formatDurationLabels(seconds: number, options?: { labels?: 'shor { unit: 'hour', value: 3600 }, { unit: 'minute', value: 60 }, { unit: 'second', value: 1 }, - ]; + ] - const labels = options?.labels ?? 'long'; - const results = []; + const labels = options?.labels ?? 'long' + const results = [] units.forEach(({ unit, value }) => { - const unitValue = Math.floor(seconds / value); + const unitValue = Math.floor(seconds / value) if (unitValue > 0) { - results.push(formatUnit(unitValue, { unit, decimals: 0, unitDisplay: labels })); - seconds %= value; + results.push(formatUnit(unitValue, { unit, decimals: 0, unitDisplay: labels })) + seconds %= value } - }); + }) - const milliseconds = Math.floor((seconds % 1) * 1000); - if (milliseconds > 0) results.push(formatUnit(milliseconds, { unit: 'millisecond', decimals: 0, unitDisplay: labels })); - return results.join(' '); + const milliseconds = Math.floor((seconds % 1) * 1000) + if (milliseconds > 0) results.push(formatUnit(milliseconds, { unit: 'millisecond', decimals: 0, unitDisplay: labels })) + return results.join(' ') } /** * Format time into duration 00:00:00 */ export function formatDurationNumbers(seconds: number): string { - const h = Math.floor(seconds / 3600); - const m = Math.floor((seconds % 3600) / 60); - const s = Math.floor(seconds % 60); - const ms = Math.floor((seconds % 1) * 1000); + const h = Math.floor(seconds / 3600) + const m = Math.floor((seconds % 3600) / 60) + const s = Math.floor(seconds % 60) + const ms = Math.floor((seconds % 1) * 1000) - const timeParts = [h, m, s].map((value) => value.toString().padStart(2, '0')); + const timeParts = [h, m, s].map((value) => value.toString().padStart(2, '0')) if (ms > 0) { - const msString = Math.floor(ms / 10).toString().padStart(2, '0'); - timeParts.push(msString); + const msString = Math.floor(ms / 10).toString().padStart(2, '0') + timeParts.push(msString) } - return timeParts.join(':'); + return timeParts.join(':') } /** * Format numbers into words */ export function formatNumberToWords(number: number): string { - if (number === 0) return numberUnderTwenty[0]; + if (number === 0) return numberUnderTwenty[0] const formatGroup = (num: number): string => { - if (num < 20) return numberUnderTwenty[num]; - if (num < 100) return `${numberTens[Math.floor(num / 10) - 2]}${num % 10 ? '-' + numberUnderTwenty[num % 10] : ''}`; - return `${numberUnderTwenty[Math.floor(num / 100)]} hundred${num % 100 ? ` and ${formatGroup(num % 100)}` : ''}`; - }; + if (num < 20) return numberUnderTwenty[num] + if (num < 100) return `${numberTens[Math.floor(num / 10) - 2]}${num % 10 ? '-' + numberUnderTwenty[num % 10] : ''}` + return `${numberUnderTwenty[Math.floor(num / 100)]} hundred${num % 100 ? ` and ${formatGroup(num % 100)}` : ''}` + } - let result = ''; - let scaleIndex = 0; + let result = '' + let scaleIndex = 0 while (number > 0) { - const groupValue = number % 1000; + const groupValue = number % 1000 if (groupValue > 0) { - result = `${formatGroup(groupValue)}${numberScales[scaleIndex]}${result ? ', ' + result : ''}`; + result = `${formatGroup(groupValue)}${numberScales[scaleIndex]}${result ? ', ' + result : ''}` } - number = Math.floor(number / 1000); - scaleIndex++; + number = Math.floor(number / 1000) + scaleIndex++ } - return result.trim(); + return result.trim() } /** diff --git a/src/generators.test.ts b/src/generators.test.ts index eed37771..d4ceb361 100644 --- a/src/generators.test.ts +++ b/src/generators.test.ts @@ -45,10 +45,10 @@ test('generatePassword', () => { expect(mod.generatePassword({ length: 8 })).toHaveLength(8) // Uppercase - expect(mod.generatePassword({ uppercase: 2 })).toMatch(new RegExp(`[A-Z]{1,}`)) + expect(mod.generatePassword({ uppercase: 2 })).toMatch(new RegExp('[A-Z]{1,}')) // Numbers - expect(mod.generatePassword({ number: 2 })).toMatch(new RegExp(`[0-9]{1,}`)) + expect(mod.generatePassword({ number: 2 })).toMatch(new RegExp('[0-9]{1,}')) // Special expect(mod.generatePassword({ special: 2 })).toMatch(new RegExp(`[${specialChars}]{1,}`)) @@ -67,14 +67,14 @@ test('generateRandomIndex', () => { expect(() => mod.generateRandomIndex(300)).toThrow('[MODS] Max generateRandomIndex value must be less than 256') // Window - const originalWindow = global.window; + const originalWindow = global.window global.window = { // @ts-ignore - Mock Test crypto: { getRandomValues: vi.fn((arr) => { // @ts-ignore - Mock Test - arr[0] = 5; - return arr; + arr[0] = 5 + return arr }), }, } diff --git a/src/generators.ts b/src/generators.ts index 3da2c021..8131be6e 100644 --- a/src/generators.ts +++ b/src/generators.ts @@ -11,9 +11,9 @@ export function generateNumber(length: number): number { return 0 } - const min = 10 ** (length - 1); - const max = 10 ** length - 1; - return Math.floor(Math.random() * (max - min + 1) + min); + const min = 10 ** (length - 1) + const max = 10 ** length - 1 + return Math.floor(Math.random() * (max - min + 1) + min) } /** @@ -89,21 +89,21 @@ export function generateRandomIndex(max: number): number { throw new Error('[MODS] Max generateRandomIndex value must be less than 256') } - const range = 256 - (256 % max); - let randomValue; + const range = 256 - (256 % max) + let randomValue if (typeof window !== 'undefined' && window.crypto && window.crypto.getRandomValues) { do { - randomValue = window.crypto.getRandomValues(new Uint8Array(1))[0]; - } while (randomValue >= range); + randomValue = window.crypto.getRandomValues(new Uint8Array(1))[0] + } while (randomValue >= range) } else { - const crypto = require('crypto'); + const crypto = require('crypto') do { - randomValue = crypto.randomBytes(1)[0]; - } while (randomValue >= range); + randomValue = crypto.randomBytes(1)[0] + } while (randomValue >= range) } - return randomValue % max; + return randomValue % max } /** diff --git a/src/modifiers.test.ts b/src/modifiers.test.ts index d3d3edbf..e5cffc6d 100644 --- a/src/modifiers.test.ts +++ b/src/modifiers.test.ts @@ -19,11 +19,11 @@ test('endWith', () => { test('endWithout', () => { expect(mod.endWithout('www.helloworld.com', '.com')).toBe('www.helloworld') expect(mod.endWithout('helloworld.com', '.com')).toBe('helloworld') - expect(mod.endWithout('filename.txt', '.txt')).toBe('filename'); - expect(mod.endWithout('filename.txt', '.jpg')).toBe('filename.txt'); - expect(mod.endWithout('', '.txt')).toBe(''); - expect(mod.endWithout('filename.txt', '')).toBe('filename.txt'); - expect(mod.endWithout('.txt', '.txt')).toBe(''); + expect(mod.endWithout('filename.txt', '.txt')).toBe('filename') + expect(mod.endWithout('filename.txt', '.jpg')).toBe('filename.txt') + expect(mod.endWithout('', '.txt')).toBe('') + expect(mod.endWithout('filename.txt', '')).toBe('filename.txt') + expect(mod.endWithout('.txt', '.txt')).toBe('') }) test('surroundWith', () => { diff --git a/src/modifiers.ts b/src/modifiers.ts index 85d46d4a..6b1c2a05 100644 --- a/src/modifiers.ts +++ b/src/modifiers.ts @@ -69,15 +69,15 @@ export function pluralize(word: string, count: number): string { ['f', 'ves'], ['fe', 'ves'], ['y', 'ies'] - ]); + ]) for (const [suffix, replacement] of suffixRules) { if (word.endsWith(suffix)) { - return word.slice(0, -suffix.length) + replacement; + return word.slice(0, -suffix.length) + replacement } } - return word + 's'; + return word + 's' } /** @@ -90,7 +90,7 @@ export function singularize(value: string): string { if (unchangingPlurals.has(value)) return value for (const [singular, plural] of irregularPlurals) { - if (plural === value) return singular; + if (plural === value) return singular } const singularRules = new Map string>([ @@ -106,11 +106,11 @@ export function singularize(value: string): string { ['i', value => value.slice(0, -1) + 'us'], ['a', value => value.slice(0, -1) + 'on'], ['s', value => value.length > 1 ? value.slice(0, -1) : value] - ]); + ]) for (const [suffix, transform] of singularRules) { if (value.endsWith(suffix)) { - return transform(value); + return transform(value) } } @@ -175,7 +175,7 @@ export function stripEmojis(text: string): string { .replace(/[\u{1FA00}-\u{1FA6F}]/gu, '') // Chess Symbols .replace(/[\u{1FA70}-\u{1FAFF}]/gu, '') // Symbols and Pictographs Extended-A .replace(/[\u{2600}-\u{26FF}]/gu, '') // Miscellaneous Symbols - .replace(/[\u{2700}-\u{27BF}]/gu, ''); // Dingbats + .replace(/[\u{2700}-\u{27BF}]/gu, '') // Dingbats } /** diff --git a/src/numbers.ts b/src/numbers.ts index 440f17bb..9ef3b02b 100644 --- a/src/numbers.ts +++ b/src/numbers.ts @@ -15,7 +15,7 @@ export function sum(numbers: number[]): number { */ export function mean(numbers: number[]): number { if (numbers.length === 0) { - console.log("[MODS] mean array is empty.") + console.log('[MODS] mean array is empty.') return 0 } const sum = numbers.reduce((acc, val) => acc + val, 0) @@ -56,7 +56,7 @@ export function subtractMargin(value: number, percentage: number): number { */ export function addMarkup(value: number, percentage: number): number { if (value === 0) { - console.log("[MODS] addMarkup value is 0.") + console.log('[MODS] addMarkup value is 0.') return 0 } return Math.round(value * (1 + percentage / 100) * 100) / 100 @@ -67,7 +67,7 @@ export function addMarkup(value: number, percentage: number): number { */ export function subtractMarkup(value: number, percentage: number): number { if (value === 0) { - console.log("[MODS] subtractMarkup value is 0.") + console.log('[MODS] subtractMarkup value is 0.') return 0 } return Math.round((value / (1 + percentage / 100)) * 100) / 100 @@ -119,7 +119,7 @@ export function mode(numbers: number[]): number[] | null { */ export function min(numbers: number[]): number { if (numbers.length === 0) { - console.log("[MODS] min array is empty.") + console.log('[MODS] min array is empty.') return 0 } return Math.min(...numbers) @@ -130,7 +130,7 @@ export function min(numbers: number[]): number { */ export function max(numbers: number[]): number { if (numbers.length === 0) { - console.log("[MODS] max array is empty.") + console.log('[MODS] max array is empty.') return 0 } return Math.max(...numbers) @@ -141,7 +141,7 @@ export function max(numbers: number[]): number { */ export function minMax(numbers: number[]): [number, number] { if (numbers.length === 0) { - console.log("[MODS] minMax array is empty.") + console.log('[MODS] minMax array is empty.') return [0, 0] } return [min(numbers), max(numbers)] @@ -152,7 +152,7 @@ export function minMax(numbers: number[]): [number, number] { */ export function range(numbers: number[]): number { if (numbers.length === 0) { - console.log("[MODS] range array is empty.") + console.log('[MODS] range array is empty.') return NaN } return max(numbers) - min(numbers) @@ -163,7 +163,7 @@ export function range(numbers: number[]): number { */ export function standardDeviation(numbers: number[], options?: { method: 'sample' | 'population' }): number { if (numbers.length === 0) { - console.log("[MODS] standardDeviation array is empty.") + console.log('[MODS] standardDeviation array is empty.') return NaN } options = options || { method: 'population' } @@ -178,16 +178,16 @@ export function standardDeviation(numbers: number[], options?: { method: 'sample * Returns the measure of asymmetry of the probability distribution of an array of numbers. The skewness value can be positive, zero, negative, or undefined. */ export function skewness(numbers: number[]): number { - const n = numbers.length; + const n = numbers.length if (n < 3) { - console.log("[MODS] skewness requires at least 3 numbers."); - return NaN; + console.log('[MODS] skewness requires at least 3 numbers.') + return NaN } - const meanValue = mean(numbers); - const stdDev = standardDeviation(numbers); - if (stdDev === 0) return 0; + const meanValue = mean(numbers) + const stdDev = standardDeviation(numbers) + if (stdDev === 0) return 0 - const sumCubedDeviations = numbers.reduce((acc, num) => acc + (num - meanValue) ** 3, 0); - return (n / ((n - 1) * (n - 2))) * (sumCubedDeviations / (stdDev ** 3)); + const sumCubedDeviations = numbers.reduce((acc, num) => acc + (num - meanValue) ** 3, 0) + return (n / ((n - 1) * (n - 2))) * (sumCubedDeviations / (stdDev ** 3)) } diff --git a/src/validators.test.ts b/src/validators.test.ts index 63523d6c..5c759b68 100644 --- a/src/validators.test.ts +++ b/src/validators.test.ts @@ -1,263 +1,263 @@ -import { expect, test } from "vitest"; -import * as mod from "./validators"; +import { expect, test } from 'vitest' +import * as mod from './validators' -test("isEmail", () => { +test('isEmail', () => { // Valid email addresses - expect(mod.isEmail("hello@email.com")).toBe(true); - expect(mod.isEmail("john.doe+marketing@email.hospital")).toBe(true); - expect(mod.isEmail("jane_d@example.abogado")).toBe(true); - expect(mod.isEmail("first.last@sub.domain.com")).toBe(true); - expect(mod.isEmail("_Yosemite.Sam@example.com")).toBe(true); - expect(mod.isEmail("firstname-lastname@example.com")).toBe(true); - expect(mod.isEmail("email@example.co.uk")).toBe(true); - expect(mod.isEmail("1234567890@example.com")).toBe(true); - expect(mod.isEmail("email@example.name")).toBe(true); - expect(mod.isEmail("email@example.museum")).toBe(true); - expect(mod.isEmail("email@example.travel")).toBe(true); + expect(mod.isEmail('hello@email.com')).toBe(true) + expect(mod.isEmail('john.doe+marketing@email.hospital')).toBe(true) + expect(mod.isEmail('jane_d@example.abogado')).toBe(true) + expect(mod.isEmail('first.last@sub.domain.com')).toBe(true) + expect(mod.isEmail('_Yosemite.Sam@example.com')).toBe(true) + expect(mod.isEmail('firstname-lastname@example.com')).toBe(true) + expect(mod.isEmail('email@example.co.uk')).toBe(true) + expect(mod.isEmail('1234567890@example.com')).toBe(true) + expect(mod.isEmail('email@example.name')).toBe(true) + expect(mod.isEmail('email@example.museum')).toBe(true) + expect(mod.isEmail('email@example.travel')).toBe(true) // Invalid email addresses - expect(mod.isEmail("helloemail.com")).toBe(false); // Missing @ - expect(mod.isEmail("plainaddress")).toBe(false); // Missing @ and domain - expect(mod.isEmail("@missing-local-part.com")).toBe(false); // Missing local part - expect(mod.isEmail("missing-at-sign.com")).toBe(false); // Missing @ - expect(mod.isEmail("missing-domain@.com")).toBe(false); // Missing domain - expect(mod.isEmail("missing-tld@domain.")).toBe(false); // Missing TLD - expect(mod.isEmail("missingdot@com")).toBe(false); // Missing dot in domain - expect(mod.isEmail("two..dots@example.com")).toBe(false); // Consecutive dots in local part - expect(mod.isEmail("john.doe@example..com")).toBe(false); // Consecutive dots in domain - expect(mod.isEmail("jane_d@example.abogado@com")).toBe(false); // Two @ signs - expect(mod.isEmail("hello@123.123.123.123")).toBe(false); // IP address instead of domain - expect(mod.isEmail("john.doe.@example.com")).toBe(false); // Trailing dot in local part - expect(mod.isEmail("john..doe@example.com")).toBe(false); // Consecutive dots in local part - expect(mod.isEmail("john.doe@example")).toBe(false); // Missing TLD - expect(mod.isEmail("jd+m@email.c")).toBe(false); // TLD too short - expect(mod.isEmail("h#email.com")).toBe(false); // Invalid character in local part -}); - -test("isNumber", () => { - expect(mod.isNumber(0)).toBe(true); - expect(mod.isNumber(123)).toBe(true); - expect(mod.isNumber("123")).toBe(false); - expect(mod.isNumber("abc")).toBe(false); -}); - -test("isUrl", () => { - expect(mod.isUrl("https://usemods.com")).toBe(true); - expect(mod.isUrl("https://www.usemods.com")).toBe(true); - expect(mod.isUrl("ftp://192.168.0.1")).toBe(true); - expect(mod.isUrl("www.usemods")).toBe(false); - expect(mod.isUrl("usemods.com")).toBe(false); - expect(mod.isUrl("com.usemods")).toBe(false); - expect(mod.isUrl("usemods")).toBe(false); -}); - -test("isUuid", () => { - expect(mod.isUuid("a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11")).toBe(true); - expect(mod.isUuid("hello")).toBe(false); -}); - -test("isJson", () => { - expect(mod.isJson('{"hello": "world"}')).toBe(true); - expect(mod.isJson('{"hello": world}')).toBe(false); - expect(mod.isJson("hello")).toBe(false); -}); - -test("isHex", () => { - expect(mod.isHex("#ffffff")).toBe(true); - expect(mod.isHex("#gggggg")).toBe(false); -}); - -test("isEmpty", () => { - expect(mod.isEmpty("")).toBe(true); - expect(mod.isEmpty([])).toBe(true); - expect(mod.isEmpty({})).toBe(true); - expect(mod.isEmpty(null)).toBe(true); - expect(mod.isEmpty(undefined)).toBe(true); - expect(mod.isEmpty("hello")).toBe(false); - expect(mod.isEmpty([1])).toBe(false); - expect(mod.isEmpty({ key: "value" })).toBe(false); -}); - -test("isAlphabetic", () => { - expect(mod.isAlphabetic("hello")).toBe(true); - expect(mod.isAlphabetic("hello123")).toBe(false); - expect(mod.isAlphabetic(123)).toBe(false); - expect(mod.isAlphabetic(["a", "b", "c"])).toBe(false); -}); - -test("isAlphanumeric", () => { - expect(mod.isAlphanumeric("hello")).toBe(true); + expect(mod.isEmail('helloemail.com')).toBe(false) // Missing @ + expect(mod.isEmail('plainaddress')).toBe(false) // Missing @ and domain + expect(mod.isEmail('@missing-local-part.com')).toBe(false) // Missing local part + expect(mod.isEmail('missing-at-sign.com')).toBe(false) // Missing @ + expect(mod.isEmail('missing-domain@.com')).toBe(false) // Missing domain + expect(mod.isEmail('missing-tld@domain.')).toBe(false) // Missing TLD + expect(mod.isEmail('missingdot@com')).toBe(false) // Missing dot in domain + expect(mod.isEmail('two..dots@example.com')).toBe(false) // Consecutive dots in local part + expect(mod.isEmail('john.doe@example..com')).toBe(false) // Consecutive dots in domain + expect(mod.isEmail('jane_d@example.abogado@com')).toBe(false) // Two @ signs + expect(mod.isEmail('hello@123.123.123.123')).toBe(false) // IP address instead of domain + expect(mod.isEmail('john.doe.@example.com')).toBe(false) // Trailing dot in local part + expect(mod.isEmail('john..doe@example.com')).toBe(false) // Consecutive dots in local part + expect(mod.isEmail('john.doe@example')).toBe(false) // Missing TLD + expect(mod.isEmail('jd+m@email.c')).toBe(false) // TLD too short + expect(mod.isEmail('h#email.com')).toBe(false) // Invalid character in local part +}) + +test('isNumber', () => { + expect(mod.isNumber(0)).toBe(true) + expect(mod.isNumber(123)).toBe(true) + expect(mod.isNumber('123')).toBe(false) + expect(mod.isNumber('abc')).toBe(false) +}) + +test('isUrl', () => { + expect(mod.isUrl('https://usemods.com')).toBe(true) + expect(mod.isUrl('https://www.usemods.com')).toBe(true) + expect(mod.isUrl('ftp://192.168.0.1')).toBe(true) + expect(mod.isUrl('www.usemods')).toBe(false) + expect(mod.isUrl('usemods.com')).toBe(false) + expect(mod.isUrl('com.usemods')).toBe(false) + expect(mod.isUrl('usemods')).toBe(false) +}) + +test('isUuid', () => { + expect(mod.isUuid('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11')).toBe(true) + expect(mod.isUuid('hello')).toBe(false) +}) + +test('isJson', () => { + expect(mod.isJson('{"hello": "world"}')).toBe(true) + expect(mod.isJson('{"hello": world}')).toBe(false) + expect(mod.isJson('hello')).toBe(false) +}) + +test('isHex', () => { + expect(mod.isHex('#ffffff')).toBe(true) + expect(mod.isHex('#gggggg')).toBe(false) +}) + +test('isEmpty', () => { + expect(mod.isEmpty('')).toBe(true) + expect(mod.isEmpty([])).toBe(true) + expect(mod.isEmpty({})).toBe(true) + expect(mod.isEmpty(null)).toBe(true) + expect(mod.isEmpty(undefined)).toBe(true) + expect(mod.isEmpty('hello')).toBe(false) + expect(mod.isEmpty([1])).toBe(false) + expect(mod.isEmpty({ key: 'value' })).toBe(false) +}) + +test('isAlphabetic', () => { + expect(mod.isAlphabetic('hello')).toBe(true) + expect(mod.isAlphabetic('hello123')).toBe(false) + expect(mod.isAlphabetic(123)).toBe(false) + expect(mod.isAlphabetic(['a', 'b', 'c'])).toBe(false) +}) + +test('isAlphanumeric', () => { + expect(mod.isAlphanumeric('hello')).toBe(true) // @ts-expect-error - Testing invalid input - expect(mod.isAlphanumeric(123)).toBe(true); - expect(mod.isAlphanumeric("hello123")).toBe(true); - expect(mod.isAlphanumeric("hello!")).toBe(false); + expect(mod.isAlphanumeric(123)).toBe(true) + expect(mod.isAlphanumeric('hello123')).toBe(true) + expect(mod.isAlphanumeric('hello!')).toBe(false) // @ts-expect-error - Testing invalid input - expect(mod.isAlphanumeric(["a", 2, "!"])).toBe(false); -}); + expect(mod.isAlphanumeric(['a', 2, '!'])).toBe(false) +}) -test("isArray", () => { - expect(mod.isArray([])).toBe(true); - expect(mod.isArray(["hello"])).toBe(true); +test('isArray', () => { + expect(mod.isArray([])).toBe(true) + expect(mod.isArray(['hello'])).toBe(true) // @ts-expect-error - Testing invalid input - expect(mod.isArray("hello")).toBe(false); -}); + expect(mod.isArray('hello')).toBe(false) +}) -test("isObject", () => { - expect(mod.isObject({ hello: "world" })).toBe(true); +test('isObject', () => { + expect(mod.isObject({ hello: 'world' })).toBe(true) // @ts-expect-error - Testing invalid input - expect(mod.isObject("hello")).toBe(false); -}); + expect(mod.isObject('hello')).toBe(false) +}) -test("isBoolean", () => { - expect(mod.isBoolean(true)).toBe(true); +test('isBoolean', () => { + expect(mod.isBoolean(true)).toBe(true) // @ts-expect-error - Testing invalid input - expect(mod.isBoolean(1)).toBe(false); + expect(mod.isBoolean(1)).toBe(false) // @ts-expect-error - Testing invalid input - expect(mod.isBoolean("hello")).toBe(false); -}); - -test("isDate", () => { - expect(mod.isDate(new Date())).toBe(true); - expect(mod.isDate("2024-01-01")).toBe(true); - expect(mod.isDate("2024-01-day")).toBe(false); - expect(mod.isDate("hello")).toBe(false); + expect(mod.isBoolean('hello')).toBe(false) +}) + +test('isDate', () => { + expect(mod.isDate(new Date())).toBe(true) + expect(mod.isDate('2024-01-01')).toBe(true) + expect(mod.isDate('2024-01-day')).toBe(false) + expect(mod.isDate('hello')).toBe(false) // @ts-expect-error - Testing invalid date object - expect(mod.isDate({ year: 2024, month: 1, day: 1 })).toBe(false); -}); - -test("isPort", () => { - expect(mod.isPort(80)).toBe(true); - expect(mod.isPort(65535)).toBe(true); - expect(mod.isPort(65536)).toBe(false); - expect(mod.isPort(-1)).toBe(false); + expect(mod.isDate({ year: 2024, month: 1, day: 1 })).toBe(false) +}) + +test('isPort', () => { + expect(mod.isPort(80)).toBe(true) + expect(mod.isPort(65535)).toBe(true) + expect(mod.isPort(65536)).toBe(false) + expect(mod.isPort(-1)).toBe(false) // @ts-expect-error - Testing invalid input - expect(mod.isPort("hello")).toBe(false); + expect(mod.isPort('hello')).toBe(false) }) -test("isUndefined", () => { - expect(mod.isUndefined(undefined)).toBe(true); +test('isUndefined', () => { + expect(mod.isUndefined(undefined)).toBe(true) // @ts-expect-error - Testing invalid input - expect(mod.isUndefined("hello")).toBe(false); -}); + expect(mod.isUndefined('hello')).toBe(false) +}) -test("isNull", () => { - expect(mod.isNull(null)).toBe(true); +test('isNull', () => { + expect(mod.isNull(null)).toBe(true) // @ts-expect-error - Testing invalid input - expect(mod.isNull("hello")).toBe(false); -}); - -test("isTime", () => { - expect(mod.isTime("12:00")).toBe(true); - expect(mod.isTime("hello")).toBe(false); -}); - -test("isLeapYear", () => { - expect(mod.isLeapYear(2020)).toBe(true); - expect(mod.isLeapYear(2021)).toBe(false); -}); - -test("isEven", () => { - expect(mod.isEven(2)).toBe(true); - expect(mod.isEven(3)).toBe(false); -}); - -test("isOdd", () => { - expect(mod.isOdd(2)).toBe(false); - expect(mod.isOdd(3)).toBe(true); -}); - -test("isPositive", () => { - expect(mod.isPositive(2)).toBe(true); - expect(mod.isPositive(-2)).toBe(false); -}); - -test("isNegative", () => { - expect(mod.isNegative(2)).toBe(false); - expect(mod.isNegative(-2)).toBe(true); -}); - -test("isPrime", () => { - expect(mod.isPrime(2)).toBe(true); - expect(mod.isPrime(4)).toBe(false); -}); - -test("isInteger", () => { - expect(mod.isInteger(2)).toBe(true); - expect(mod.isInteger(-2)).toBe(true); - expect(mod.isInteger(2.5)).toBe(false); - expect(mod.isInteger("hello")).toBe(false); - expect(mod.isInteger("2")).toBe(false); - expect(mod.isInteger({})).toBe(false); - expect(mod.isInteger([])).toBe(false); - expect(mod.isInteger(null)).toBe(false); - expect(mod.isInteger(true)).toBe(false); - expect(mod.isInteger(undefined)).toBe(false); -}); - -test("isFloat", () => { - expect(mod.isFloat(2.5)).toBe(true); - expect(mod.isFloat(2)).toBe(false); - expect(mod.isFloat("hello")).toBe(false); - expect(mod.isFloat({})).toBe(false); - expect(mod.isFloat([])).toBe(false); - expect(mod.isFloat(null)).toBe(false); - expect(mod.isFloat(true)).toBe(false); - expect(mod.isFloat(undefined)).toBe(false); -}); - -test("isBetween", () => { - expect(mod.isBetween(4, 2, 6)).toBe(true); - expect(mod.isBetween(4, 6, 2)).toBe(true); - expect(mod.isBetween(4, 6, 8)).toBe(false); -}); - -test("isDivisibleBy", () => { - expect(mod.isDivisibleBy(4, 2)).toBe(true); - expect(mod.isDivisibleBy(4, 3)).toBe(false); -}); - -test("isOver9000", () => { - expect(mod.isOver9000(9001)).toBe(true); - expect(mod.isOver9000(9000)).toBe(false); -}); - -test("isCreditCard", () => { - expect(mod.isCreditCard("4111111111111111")).toBe(true); - expect(mod.isCreditCard(4111111111111111)).toBe(true); - expect(mod.isCreditCard("hello")).toBe(false); - expect(mod.isCreditCard({})).toBe(false); - expect(mod.isCreditCard(1234567890123456)).toBe(false); -}); - -test("isZero", () => { - expect(mod.isZero(0)).toBe(true); - expect(mod.isZero(1)).toBe(false); -}); - -test("isIpAddress", () => { - expect(mod.isIpAddress("192.168.0.1")).toBe(true); - expect(mod.isIpAddress("192.168.0.1:3000")).toBe(true); - expect(mod.isIpAddress("00:00:00:00:00:00")).toBe(false); - expect(mod.isIpAddress("hello")).toBe(false); -}); - -test("isMacAddress", () => { - expect(mod.isMacAddress("00:00:00:00:00:00")).toBe(true); - expect(mod.isMacAddress("hello")).toBe(false); -}); - -test("isLatLng", () => { - expect(mod.isLatLng("12.345678,-98.765432")).toBe(true); - expect(mod.isLatLng("12.345678, -98.765432")).toBe(true); - expect(mod.isLatLng("98.765432,12.345678")).toBe(false); - expect(mod.isLatLng("hello")).toBe(false); -}); - -test("isLatitude", () => { - expect(mod.isLatitude("12.345678")).toBe(true); - expect(mod.isLatitude("hello")).toBe(false); -}); - -test("isLongitude", () => { - expect(mod.isLongitude("-98.765432")).toBe(true); - expect(mod.isLongitude("hello")).toBe(false); -}); + expect(mod.isNull('hello')).toBe(false) +}) + +test('isTime', () => { + expect(mod.isTime('12:00')).toBe(true) + expect(mod.isTime('hello')).toBe(false) +}) + +test('isLeapYear', () => { + expect(mod.isLeapYear(2020)).toBe(true) + expect(mod.isLeapYear(2021)).toBe(false) +}) + +test('isEven', () => { + expect(mod.isEven(2)).toBe(true) + expect(mod.isEven(3)).toBe(false) +}) + +test('isOdd', () => { + expect(mod.isOdd(2)).toBe(false) + expect(mod.isOdd(3)).toBe(true) +}) + +test('isPositive', () => { + expect(mod.isPositive(2)).toBe(true) + expect(mod.isPositive(-2)).toBe(false) +}) + +test('isNegative', () => { + expect(mod.isNegative(2)).toBe(false) + expect(mod.isNegative(-2)).toBe(true) +}) + +test('isPrime', () => { + expect(mod.isPrime(2)).toBe(true) + expect(mod.isPrime(4)).toBe(false) +}) + +test('isInteger', () => { + expect(mod.isInteger(2)).toBe(true) + expect(mod.isInteger(-2)).toBe(true) + expect(mod.isInteger(2.5)).toBe(false) + expect(mod.isInteger('hello')).toBe(false) + expect(mod.isInteger('2')).toBe(false) + expect(mod.isInteger({})).toBe(false) + expect(mod.isInteger([])).toBe(false) + expect(mod.isInteger(null)).toBe(false) + expect(mod.isInteger(true)).toBe(false) + expect(mod.isInteger(undefined)).toBe(false) +}) + +test('isFloat', () => { + expect(mod.isFloat(2.5)).toBe(true) + expect(mod.isFloat(2)).toBe(false) + expect(mod.isFloat('hello')).toBe(false) + expect(mod.isFloat({})).toBe(false) + expect(mod.isFloat([])).toBe(false) + expect(mod.isFloat(null)).toBe(false) + expect(mod.isFloat(true)).toBe(false) + expect(mod.isFloat(undefined)).toBe(false) +}) + +test('isBetween', () => { + expect(mod.isBetween(4, 2, 6)).toBe(true) + expect(mod.isBetween(4, 6, 2)).toBe(true) + expect(mod.isBetween(4, 6, 8)).toBe(false) +}) + +test('isDivisibleBy', () => { + expect(mod.isDivisibleBy(4, 2)).toBe(true) + expect(mod.isDivisibleBy(4, 3)).toBe(false) +}) + +test('isOver9000', () => { + expect(mod.isOver9000(9001)).toBe(true) + expect(mod.isOver9000(9000)).toBe(false) +}) + +test('isCreditCard', () => { + expect(mod.isCreditCard('4111111111111111')).toBe(true) + expect(mod.isCreditCard(4111111111111111)).toBe(true) + expect(mod.isCreditCard('hello')).toBe(false) + expect(mod.isCreditCard({})).toBe(false) + expect(mod.isCreditCard(1234567890123456)).toBe(false) +}) + +test('isZero', () => { + expect(mod.isZero(0)).toBe(true) + expect(mod.isZero(1)).toBe(false) +}) + +test('isIpAddress', () => { + expect(mod.isIpAddress('192.168.0.1')).toBe(true) + expect(mod.isIpAddress('192.168.0.1:3000')).toBe(true) + expect(mod.isIpAddress('00:00:00:00:00:00')).toBe(false) + expect(mod.isIpAddress('hello')).toBe(false) +}) + +test('isMacAddress', () => { + expect(mod.isMacAddress('00:00:00:00:00:00')).toBe(true) + expect(mod.isMacAddress('hello')).toBe(false) +}) + +test('isLatLng', () => { + expect(mod.isLatLng('12.345678,-98.765432')).toBe(true) + expect(mod.isLatLng('12.345678, -98.765432')).toBe(true) + expect(mod.isLatLng('98.765432,12.345678')).toBe(false) + expect(mod.isLatLng('hello')).toBe(false) +}) + +test('isLatitude', () => { + expect(mod.isLatitude('12.345678')).toBe(true) + expect(mod.isLatitude('hello')).toBe(false) +}) + +test('isLongitude', () => { + expect(mod.isLongitude('-98.765432')).toBe(true) + expect(mod.isLongitude('hello')).toBe(false) +}) diff --git a/src/validators.ts b/src/validators.ts index 0ba5b32f..8d74ebe6 100644 --- a/src/validators.ts +++ b/src/validators.ts @@ -7,17 +7,17 @@ */ export function isEmail(value: string): boolean { const regex = - /^(?!.*[._+-]{2})(?!.*[._+-]$)[a-zA-Z0-9._+-]+(? 9000; + return value > 9000 } /** * Check if the number is a prime number. */ export function isPrime(value: number): boolean { - const boundary = Math.floor(Math.sqrt(value)); + const boundary = Math.floor(Math.sqrt(value)) for (let i = 2; i <= boundary; i++) { - if (value % i === 0) return false; + if (value % i === 0) return false } - return value >= 2; + return value >= 2 } /** * Check if the number is an integer. */ export function isInteger(value: any): boolean { - if (typeof value !== "number") return false; - return (value) % 1 === 0; + if (typeof value !== 'number') return false + return (value) % 1 === 0 } /** * Check if the number is a float. */ export function isFloat(value: any): boolean { - if (typeof value !== "number") return false; - return !isInteger(value); + if (typeof value !== 'number') return false + return !isInteger(value) } /** @@ -230,27 +230,27 @@ export function isFloat(value: any): boolean { */ export function isBetween(value: number, min: number, max: number): boolean { if (min > max) { - [min, max] = [max, min]; + [min, max] = [max, min] } - return value >= min && value <= max; + return value >= min && value <= max } /** * Check if the number is divisible by the specified number. */ export function isDivisibleBy(value: number, divisor: number): boolean { - return value % divisor === 0; + return value % divisor === 0 } /** * Check if any given value is a valid credit card number. */ export function isCreditCard(value: any): boolean { - if (typeof value === "number") value = value.toString(); - if (typeof value !== "string") return false; + if (typeof value === 'number') value = value.toString() + if (typeof value !== 'string') return false const regex = - /^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:0111|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11})$/; - return regex.test(value); + /^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:0111|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11})$/ + return regex.test(value) } /** @@ -258,24 +258,24 @@ export function isCreditCard(value: any): boolean { */ export function isLatLng(value: string): boolean { const regex = - /^([-+]?([1-8]?\d(\.\d+)?|90(\.0+)?)),\s*([-+]?(180(\.0+)?|((1[0-7]\d)|([1-9]?\d))(\.\d+)?))$/; - return regex.test(value); + /^([-+]?([1-8]?\d(\.\d+)?|90(\.0+)?)),\s*([-+]?(180(\.0+)?|((1[0-7]\d)|([1-9]?\d))(\.\d+)?))$/ + return regex.test(value) } /** * Check if any given value is a valid latitude coordinate. */ export function isLatitude(value: string): boolean { - const regex = /^[-+]?([1-8]?\d(\.\d{1,6})?|90(\.0{1,6})?)$/; - return regex.test(value); + const regex = /^[-+]?([1-8]?\d(\.\d{1,6})?|90(\.0{1,6})?)$/ + return regex.test(value) } /** * Check if any given value is a valid longitude coordinate. */ export function isLongitude(value: string): boolean { - const regex = /^[-+]?(180(\.0{1,6})?|((1[0-7]\d)|([1-9]?\d))(\.\d{1,6})?)$/; - return regex.test(value); + const regex = /^[-+]?(180(\.0{1,6})?|((1[0-7]\d)|([1-9]?\d))(\.\d{1,6})?)$/ + return regex.test(value) } /** @@ -283,23 +283,23 @@ export function isLongitude(value: string): boolean { */ export function isIpAddress(value: string): boolean { const regex = - /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)((?::\d+)?|)$/; - return regex.test(value); + /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)((?::\d+)?|)$/ + return regex.test(value) } /** * Check if any given value is a valid port number. */ export function isPort(value: number): boolean { - return value > 0 && value <= 65535; + return value > 0 && value <= 65535 } /** * Check if any given value is a valid MAC address. */ export function isMacAddress(value: string): boolean { - const regex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/; - return regex.test(value); + const regex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/ + return regex.test(value) } // /** From b7c5c8cdcff7b497ef61499f4915943bdbe2280a Mon Sep 17 00:00:00 2001 From: Jeremy Butler Date: Fri, 24 May 2024 16:56:43 +1000 Subject: [PATCH 38/41] Refactor validators to accept unknown type --- src/validators.test.ts | 5 ++ src/validators.ts | 126 ++++------------------------------------- 2 files changed, 16 insertions(+), 115 deletions(-) diff --git a/src/validators.test.ts b/src/validators.test.ts index 5c759b68..fc7cbd38 100644 --- a/src/validators.test.ts +++ b/src/validators.test.ts @@ -51,14 +51,19 @@ test('isUrl', () => { }) test('isUuid', () => { + // Valid UUIDs expect(mod.isUuid('a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11')).toBe(true) + // False positives expect(mod.isUuid('hello')).toBe(false) + expect(mod.isUuid(1234567890)).toBe(false) }) test('isJson', () => { expect(mod.isJson('{"hello": "world"}')).toBe(true) + // False expect(mod.isJson('{"hello": world}')).toBe(false) expect(mod.isJson('hello')).toBe(false) + expect(mod.isJson(123)).toBe(false) }) test('isHex', () => { diff --git a/src/validators.ts b/src/validators.ts index 8d74ebe6..b1bbb714 100644 --- a/src/validators.ts +++ b/src/validators.ts @@ -48,7 +48,8 @@ export function isEmpty( /** * Check if any given value is a valid UUID. */ -export function isUuid(value: string): boolean { +export function isUuid(value: unknown): boolean { + if (typeof value !== 'string') return false const regex = /^[a-f\d]{8}(-[a-f\d]{4}){4}[a-f\d]{8}$/i return regex.test(value) } @@ -56,7 +57,8 @@ export function isUuid(value: string): boolean { /** * Check if any given value is a valid JSON string. */ -export function isJson(value: string): boolean { +export function isJson(value: unknown): boolean { + if (typeof value !== 'string') return false try { JSON.parse(value) return true @@ -68,8 +70,9 @@ export function isJson(value: string): boolean { /** * Check if any given value is an object. */ -export function isObject(value: object): boolean { - return value && typeof value === 'object' && value.constructor === Object +export function isObject(value: unknown): boolean { + if (typeof value !== 'object' || value === null) return false + return value.constructor === Object } /** @@ -212,7 +215,7 @@ export function isPrime(value: number): boolean { /** * Check if the number is an integer. */ -export function isInteger(value: any): boolean { +export function isInteger(value: unknown): boolean { if (typeof value !== 'number') return false return (value) % 1 === 0 } @@ -220,7 +223,7 @@ export function isInteger(value: any): boolean { /** * Check if the number is a float. */ -export function isFloat(value: any): boolean { +export function isFloat(value: unknown): boolean { if (typeof value !== 'number') return false return !isInteger(value) } @@ -245,7 +248,7 @@ export function isDivisibleBy(value: number, divisor: number): boolean { /** * Check if any given value is a valid credit card number. */ -export function isCreditCard(value: any): boolean { +export function isCreditCard(value: unknown): boolean { if (typeof value === 'number') value = value.toString() if (typeof value !== 'string') return false const regex = @@ -300,111 +303,4 @@ export function isPort(value: number): boolean { export function isMacAddress(value: string): boolean { const regex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/ return regex.test(value) -} - -// /** -// * Check if you're a passionate iPhone fan. -// */ -// export function isIos(): boolean { -// return /iPad|iPhone|iPod/.test(navigator.platform); -// } - -// /** -// * Check if you're a fervent Windows fan. -// */ -// export function isWindows(): boolean { -// return /Win/.test(navigator.platform); -// } - -// /** -// * Check if you're a devoted Linux fan. -// */ -// export function isLinux(): boolean { -// return /Linux/.test(navigator.platform); -// } - -// /** -// * Check if you're a zealous Android fan. -// */ -// export function isAndroid(): boolean { -// return /Android/.test(navigator.platform); -// } - -// /** -// * Check if you're a staunch Mac fan. -// */ -// export function isMac(): boolean { -// return /Mac/.test(navigator.platform); -// } - -// /** -// * Check if you're a die-hard Chrome fan. -// */ -// export function isChrome(): boolean { -// return /Chrome/.test(navigator.userAgent); -// } - -// /** -// * Check if you're a dedicated Firefox fan. -// */ -// export function isFirefox(): boolean { -// return /Firefox/.test(navigator.userAgent); -// } - -// /** -// * Check if you're a lonely Safari fan. -// */ -// export function isSafari(): boolean { -// return /Safari/.test(navigator.userAgent); -// } - -// /** -// * Check if you're an ardent Edge fan. -// */ -// export function isEdge(): boolean { -// return /Edge/.test(navigator.userAgent); -// } - -// /** -// * Check if you're rocking a mobile -// */ -// export function isMobile(): boolean { -// return /Mobi/.test(navigator.userAgent); -// } - -// /** -// * Check if you're tablet user -// */ -// export function isTablet(): boolean { -// return /Tablet/.test(navigator.userAgent); -// } - -// /** -// * Check if you're pro desktop user -// */ -// export function isDesktop(): boolean { -// return !isMobile() && !isTablet(); -// } - -// /** -// * Check if you're portrait -// */ -// export function isPortrait(): boolean { -// return window.innerHeight > window.innerWidth; -// } - -// /** -// * Check if you're landscape -// */ -// export function isLandscape(): boolean { -// return window.innerWidth > window.innerHeight; -// } - -// /** -// * Check if you're a cyborg or a bot -// */ -// export function isBot(): boolean { -// return /bot|googlebot|crawler|spider|robot|crawling/i.test( -// navigator.userAgent -// ); -// } +} \ No newline at end of file From 967c574fb4f75d5721da33d30745e22f1c080380 Mon Sep 17 00:00:00 2001 From: Jeremy Butler Date: Fri, 24 May 2024 16:58:25 +1000 Subject: [PATCH 39/41] ESlint updates --- nuxt-module/src/runtime/utils/config.ts | 450 ++++++++++++------------ 1 file changed, 225 insertions(+), 225 deletions(-) diff --git a/nuxt-module/src/runtime/utils/config.ts b/nuxt-module/src/runtime/utils/config.ts index 3d66d9af..a89bb8bf 100644 --- a/nuxt-module/src/runtime/utils/config.ts +++ b/nuxt-module/src/runtime/utils/config.ts @@ -1,244 +1,244 @@ export const currencySymbols = new Map([ - ['en-US', 'USD'], // US Dollar - ['en-GB', 'GBP'], // British Pound - ['en-AU', 'AUD'], // Australian Dollar - ['en-CA', 'CAD'], // Canadian Dollar - ['en-NZ', 'NZD'], // New Zealand Dollar - ['en-ZA', 'ZAR'], // South African Rand - ['de-DE', 'EUR'], // Euro (Germany) - ['fr-FR', 'EUR'], // Euro (France) - ['es-ES', 'EUR'], // Euro (Spain) - ['it-IT', 'EUR'], // Euro (Italy) - ['pt-PT', 'EUR'], // Euro (Portugal) - ['nl-NL', 'EUR'], // Euro (Netherlands) - ['fi-FI', 'EUR'], // Euro (Finland) - ['da-DK', 'DKK'], // Danish Krone - ['sv-SE', 'SEK'], // Swedish Krona - ['nb-NO', 'NOK'], // Norwegian Krone - ['pl-PL', 'PLN'], // Polish Zloty - ['tr-TR', 'TRY'], // Turkish Lira - ['ru-RU', 'RUB'], // Russian Ruble - ['ja-JP', 'JPY'], // Japanese Yen - ['zh-CN', 'CNY'], // Chinese Yuan - ['ko-KR', 'KRW'], // South Korean Won - ['ar-SA', 'SAR'], // Saudi Riyal - ['he-IL', 'ILS'], // Israeli Shekel - ['id-ID', 'IDR'], // Indonesian Rupiah - ['ms-MY', 'MYR'], // Malaysian Ringgit - ['th-TH', 'THB'], // Thai Baht - ['vi-VN', 'VND'], // Vietnamese Dong - ['hi-IN', 'INR'], // Indian Rupee - ['bn-IN', 'INR'], // Indian Rupee - ['pa-IN', 'INR'], // Indian Rupee - ['gu-IN', 'INR'], // Indian Rupee - ['or-IN', 'INR'], // Indian Rupee - ['ta-IN', 'INR'], // Indian Rupee - ['te-IN', 'INR'], // Indian Rupee - ['kn-IN', 'INR'], // Indian Rupee - ['ml-IN', 'INR'] // Indian Rupee + ['en-US', 'USD'], // US Dollar + ['en-GB', 'GBP'], // British Pound + ['en-AU', 'AUD'], // Australian Dollar + ['en-CA', 'CAD'], // Canadian Dollar + ['en-NZ', 'NZD'], // New Zealand Dollar + ['en-ZA', 'ZAR'], // South African Rand + ['de-DE', 'EUR'], // Euro (Germany) + ['fr-FR', 'EUR'], // Euro (France) + ['es-ES', 'EUR'], // Euro (Spain) + ['it-IT', 'EUR'], // Euro (Italy) + ['pt-PT', 'EUR'], // Euro (Portugal) + ['nl-NL', 'EUR'], // Euro (Netherlands) + ['fi-FI', 'EUR'], // Euro (Finland) + ['da-DK', 'DKK'], // Danish Krone + ['sv-SE', 'SEK'], // Swedish Krona + ['nb-NO', 'NOK'], // Norwegian Krone + ['pl-PL', 'PLN'], // Polish Zloty + ['tr-TR', 'TRY'], // Turkish Lira + ['ru-RU', 'RUB'], // Russian Ruble + ['ja-JP', 'JPY'], // Japanese Yen + ['zh-CN', 'CNY'], // Chinese Yuan + ['ko-KR', 'KRW'], // South Korean Won + ['ar-SA', 'SAR'], // Saudi Riyal + ['he-IL', 'ILS'], // Israeli Shekel + ['id-ID', 'IDR'], // Indonesian Rupiah + ['ms-MY', 'MYR'], // Malaysian Ringgit + ['th-TH', 'THB'], // Thai Baht + ['vi-VN', 'VND'], // Vietnamese Dong + ['hi-IN', 'INR'], // Indian Rupee + ['bn-IN', 'INR'], // Indian Rupee + ['pa-IN', 'INR'], // Indian Rupee + ['gu-IN', 'INR'], // Indian Rupee + ['or-IN', 'INR'], // Indian Rupee + ['ta-IN', 'INR'], // Indian Rupee + ['te-IN', 'INR'], // Indian Rupee + ['kn-IN', 'INR'], // Indian Rupee + ['ml-IN', 'INR'] // Indian Rupee ]) export const configLocales = new Set([ - 'en-US', // English (United States) - 'en-GB', // English (United Kingdom) - 'en-CA', // English (Canada) - 'en-AU', // English (Australia) - 'fr-FR', // French (France) - 'es-ES', // Spanish (Spain) - 'de-DE', // German (Germany) - 'it-IT', // Italian (Italy) - 'ja-JP', // Japanese (Japan) - 'ko-KR', // Korean (South Korea) - 'zh-CN', // Chinese (Simplified, China) - 'zh-TW', // Chinese (Traditional, Taiwan) - 'pt-PT', // Portuguese (Portugal) - 'pt-BR', // Portuguese (Brazil) - 'ru-RU', // Russian (Russia) - 'nl-NL', // Dutch (Netherlands) - 'da-DK', // Danish (Denmark) - 'sv-SE', // Swedish (Sweden) - 'nb-NO', // Norwegian BokmÃ¥l (Norway) - 'fi-FI', // Finnish (Finland) - 'pl-PL', // Polish (Poland) - 'tr-TR', // Turkish (Turkey) - 'ar-SA', // Arabic (Saudi Arabia) - 'he-IL', // Hebrew (Yiddish) - 'id-ID', // Indonesian (Indonesia) - 'ms-MY', // Malay (Malaysia) - 'th-TH', // Thai (Thailand) - 'vi-VN' // Vietnamese (Vietnam) + 'en-US', // English (United States) + 'en-GB', // English (United Kingdom) + 'en-CA', // English (Canada) + 'en-AU', // English (Australia) + 'fr-FR', // French (France) + 'es-ES', // Spanish (Spain) + 'de-DE', // German (Germany) + 'it-IT', // Italian (Italy) + 'ja-JP', // Japanese (Japan) + 'ko-KR', // Korean (South Korea) + 'zh-CN', // Chinese (Simplified, China) + 'zh-TW', // Chinese (Traditional, Taiwan) + 'pt-PT', // Portuguese (Portugal) + 'pt-BR', // Portuguese (Brazil) + 'ru-RU', // Russian (Russia) + 'nl-NL', // Dutch (Netherlands) + 'da-DK', // Danish (Denmark) + 'sv-SE', // Swedish (Sweden) + 'nb-NO', // Norwegian BokmÃ¥l (Norway) + 'fi-FI', // Finnish (Finland) + 'pl-PL', // Polish (Poland) + 'tr-TR', // Turkish (Turkey) + 'ar-SA', // Arabic (Saudi Arabia) + 'he-IL', // Hebrew (Yiddish) + 'id-ID', // Indonesian (Indonesia) + 'ms-MY', // Malay (Malaysia) + 'th-TH', // Thai (Thailand) + 'vi-VN' // Vietnamese (Vietnam) ]) export const configUnits = new Set([ - 'acre', - 'bit', - 'byte', - 'celsius', - 'centimeter', - 'day', - 'degree', - 'fahrenheit', - 'fluid-ounce', - 'foot', - 'gallon', - 'gigabit', - 'gigabyte', - 'gram', - 'hectare', - 'hour', - 'inch', - 'kilobit', - 'kilobyte', - 'kilogram', - 'kilometer', - 'liter', - 'megabit', - 'megabyte', - 'meter', - 'microsecond', - 'mile', - 'mile-scandinavian', - 'milliliter', - 'millimeter', - 'millisecond', - 'minute', - 'month', - 'nanosecond', - 'ounce', - 'percent', - 'petabyte', - 'pound', - 'second', - 'stone', - 'terabit', - 'terabyte', - 'week', - 'yard', - 'year' + 'acre', + 'bit', + 'byte', + 'celsius', + 'centimeter', + 'day', + 'degree', + 'fahrenheit', + 'fluid-ounce', + 'foot', + 'gallon', + 'gigabit', + 'gigabyte', + 'gram', + 'hectare', + 'hour', + 'inch', + 'kilobit', + 'kilobyte', + 'kilogram', + 'kilometer', + 'liter', + 'megabit', + 'megabyte', + 'meter', + 'microsecond', + 'mile', + 'mile-scandinavian', + 'milliliter', + 'millimeter', + 'millisecond', + 'minute', + 'month', + 'nanosecond', + 'ounce', + 'percent', + 'petabyte', + 'pound', + 'second', + 'stone', + 'terabit', + 'terabyte', + 'week', + 'yard', + 'year' ]) export const unchangingPlurals = new Set([ - 'sheep', - 'fish', - 'deer', - 'hay', - 'moose', - 'series', - 'species', - 'aircraft', - 'bison', - 'buffalo', - 'cod', - 'elk', - 'halibut', - 'hovercraft', - 'lego', - 'mackerel', - 'salmon', - 'spacecraft', - 'swine', - 'trout', - 'tuna' -]); + 'sheep', + 'fish', + 'deer', + 'hay', + 'moose', + 'series', + 'species', + 'aircraft', + 'bison', + 'buffalo', + 'cod', + 'elk', + 'halibut', + 'hovercraft', + 'lego', + 'mackerel', + 'salmon', + 'spacecraft', + 'swine', + 'trout', + 'tuna' +]) export const irregularPlurals = new Map([ - ['addendum', 'addenda'], - ['agendum', 'agenda'], - ['alumnus', 'alumni'], - ['analysis', 'analyses'], - ['anathema', 'anathemata'], - ['appendix', 'appendices'], - ['axis', 'axes'], - ['bacterium', 'bacteria'], - ['basis', 'bases'], - ['cactus', 'cacti'], - ['cherub', 'cherubim'], - ['child', 'children'], - ['corrigendum', 'corrigenda'], - ['crisis', 'crises'], - ['criterion', 'criteria'], - ['curriculum', 'curricula'], - ['custom', 'customs'], - ['datum', 'data'], - ['diagnosis', 'diagnoses'], - ['dogma', 'dogmata'], - ['ellipsis', 'ellipses'], - ['elf', 'elves'], - ['erratum', 'errata'], - ['focus', 'foci'], - ['foot', 'feet'], - ['forum', 'fora'], - ['fungus', 'fungi'], - ['genus', 'genera'], - ['goose', 'geese'], - ['half', 'halves'], - ['hypothesis', 'hypotheses'], - ['index', 'indices'], - ['knife', 'knives'], - ['leaf', 'leaves'], - ['lemma', 'lemmata'], - ['life', 'lives'], - ['loaf', 'loaves'], - ['man', 'men'], - ['matrix', 'matrices'], - ['medium', 'media'], - ['memorandum', 'memoranda'], - ['millennium', 'millennia'], - ['mouse', 'mice'], - ['nucleus', 'nuclei'], - ['oasis', 'oases'], - ['ovum', 'ova'], - ['ox', 'oxen'], - ['parenthesis', 'parentheses'], - ['person', 'people'], - ['phenomenon', 'phenomena'], - ['potato', 'potatoes'], - ['radius', 'radii'], - ['schema', 'schemata'], - ['stimulus', 'stimuli'], - ['stigma', 'stigmata'], - ['stoma', 'stomata'], - ['stratum', 'strata'], - ['syllabus', 'syllabi'], - ['symposium', 'symposia'], - ['synthesis', 'syntheses'], - ['thesis', 'theses'], - ['tooth', 'teeth'], - ['tomato', 'tomatoes'], - ['vertex', 'vertices'], - ['wife', 'wives'], - ['woman', 'women'] -]); + ['addendum', 'addenda'], + ['agendum', 'agenda'], + ['alumnus', 'alumni'], + ['analysis', 'analyses'], + ['anathema', 'anathemata'], + ['appendix', 'appendices'], + ['axis', 'axes'], + ['bacterium', 'bacteria'], + ['basis', 'bases'], + ['cactus', 'cacti'], + ['cherub', 'cherubim'], + ['child', 'children'], + ['corrigendum', 'corrigenda'], + ['crisis', 'crises'], + ['criterion', 'criteria'], + ['curriculum', 'curricula'], + ['custom', 'customs'], + ['datum', 'data'], + ['diagnosis', 'diagnoses'], + ['dogma', 'dogmata'], + ['ellipsis', 'ellipses'], + ['elf', 'elves'], + ['erratum', 'errata'], + ['focus', 'foci'], + ['foot', 'feet'], + ['forum', 'fora'], + ['fungus', 'fungi'], + ['genus', 'genera'], + ['goose', 'geese'], + ['half', 'halves'], + ['hypothesis', 'hypotheses'], + ['index', 'indices'], + ['knife', 'knives'], + ['leaf', 'leaves'], + ['lemma', 'lemmata'], + ['life', 'lives'], + ['loaf', 'loaves'], + ['man', 'men'], + ['matrix', 'matrices'], + ['medium', 'media'], + ['memorandum', 'memoranda'], + ['millennium', 'millennia'], + ['mouse', 'mice'], + ['nucleus', 'nuclei'], + ['oasis', 'oases'], + ['ovum', 'ova'], + ['ox', 'oxen'], + ['parenthesis', 'parentheses'], + ['person', 'people'], + ['phenomenon', 'phenomena'], + ['potato', 'potatoes'], + ['radius', 'radii'], + ['schema', 'schemata'], + ['stimulus', 'stimuli'], + ['stigma', 'stigmata'], + ['stoma', 'stomata'], + ['stratum', 'strata'], + ['syllabus', 'syllabi'], + ['symposium', 'symposia'], + ['synthesis', 'syntheses'], + ['thesis', 'theses'], + ['tooth', 'teeth'], + ['tomato', 'tomatoes'], + ['vertex', 'vertices'], + ['wife', 'wives'], + ['woman', 'women'] +]) export const numberUnderTwenty: string[] = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen'] export const numberTens: string[] = ['twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety'] export const numberScales: string[] = ['', ' thousand', ' million', ' billion', ' trillion', ' quadrillion', ' quintillion'] export const formatTitleExceptions = new Set([ - 'a', - 'an', - 'to', - 'the', - 'for', - 'and', - 'nor', - 'but', - 'or', - 'yet', - 'so', - 'in', - 'is', - 'it', - 'than', - 'on', - 'at', - 'with', - 'under', - 'above', - 'from', - 'of', - 'although', - 'because', - 'since', - 'unless' + 'a', + 'an', + 'to', + 'the', + 'for', + 'and', + 'nor', + 'but', + 'or', + 'yet', + 'so', + 'in', + 'is', + 'it', + 'than', + 'on', + 'at', + 'with', + 'under', + 'above', + 'from', + 'of', + 'although', + 'because', + 'since', + 'unless' ]) \ No newline at end of file From 538799f9947c8982ef85be28c10529cb77234afb Mon Sep 17 00:00:00 2001 From: Jeremy Butler Date: Fri, 24 May 2024 17:02:14 +1000 Subject: [PATCH 40/41] Add form components --- .../form/{Code.vue => FormCode.vue} | 0 .../form/{Data.vue => FormData.vue} | 0 .../form/{Dummy.vue => FormDummy.vue} | 0 .../form/{Input.vue => FormInput.vue} | 44 +++++++++---------- .../form/{Label.vue => FormLabel.vue} | 0 .../form/{Number.vue => FormNumber.vue} | 0 .../form/{Select.vue => FormSelect.vue} | 0 .../form/{Switch.vue => FormSwitch.vue} | 0 8 files changed, 22 insertions(+), 22 deletions(-) rename nuxt-web/components/form/{Code.vue => FormCode.vue} (100%) rename nuxt-web/components/form/{Data.vue => FormData.vue} (100%) rename nuxt-web/components/form/{Dummy.vue => FormDummy.vue} (100%) rename nuxt-web/components/form/{Input.vue => FormInput.vue} (53%) rename nuxt-web/components/form/{Label.vue => FormLabel.vue} (100%) rename nuxt-web/components/form/{Number.vue => FormNumber.vue} (100%) rename nuxt-web/components/form/{Select.vue => FormSelect.vue} (100%) rename nuxt-web/components/form/{Switch.vue => FormSwitch.vue} (100%) diff --git a/nuxt-web/components/form/Code.vue b/nuxt-web/components/form/FormCode.vue similarity index 100% rename from nuxt-web/components/form/Code.vue rename to nuxt-web/components/form/FormCode.vue diff --git a/nuxt-web/components/form/Data.vue b/nuxt-web/components/form/FormData.vue similarity index 100% rename from nuxt-web/components/form/Data.vue rename to nuxt-web/components/form/FormData.vue diff --git a/nuxt-web/components/form/Dummy.vue b/nuxt-web/components/form/FormDummy.vue similarity index 100% rename from nuxt-web/components/form/Dummy.vue rename to nuxt-web/components/form/FormDummy.vue diff --git a/nuxt-web/components/form/Input.vue b/nuxt-web/components/form/FormInput.vue similarity index 53% rename from nuxt-web/components/form/Input.vue rename to nuxt-web/components/form/FormInput.vue index 26b76fec..2591d278 100644 --- a/nuxt-web/components/form/Input.vue +++ b/nuxt-web/components/form/FormInput.vue @@ -14,28 +14,28 @@ diff --git a/nuxt-web/components/form/Label.vue b/nuxt-web/components/form/FormLabel.vue similarity index 100% rename from nuxt-web/components/form/Label.vue rename to nuxt-web/components/form/FormLabel.vue diff --git a/nuxt-web/components/form/Number.vue b/nuxt-web/components/form/FormNumber.vue similarity index 100% rename from nuxt-web/components/form/Number.vue rename to nuxt-web/components/form/FormNumber.vue diff --git a/nuxt-web/components/form/Select.vue b/nuxt-web/components/form/FormSelect.vue similarity index 100% rename from nuxt-web/components/form/Select.vue rename to nuxt-web/components/form/FormSelect.vue diff --git a/nuxt-web/components/form/Switch.vue b/nuxt-web/components/form/FormSwitch.vue similarity index 100% rename from nuxt-web/components/form/Switch.vue rename to nuxt-web/components/form/FormSwitch.vue From 10952a83e6fb2e3dfe8105fd007a67e3121aa31e Mon Sep 17 00:00:00 2001 From: Jeremy Butler Date: Fri, 24 May 2024 20:32:40 +1000 Subject: [PATCH 41/41] Add eslint-plugin-nuxt and enable it in eslint.config.js --- eslint.config.js | 9 +- nuxt-web/components/Button.vue | 8 +- .../content/formatters/FormatCurrency.vue | 12 +- .../content/formatters/FormatNumber.vue | 8 +- .../content/formatters/FormatPercentage.vue | 12 +- .../content/formatters/FormatUnit.vue | 22 +- nuxt-web/components/example/Code.vue | 12 +- nuxt-web/components/form/FormNumber.vue | 43 +- package-lock.json | 16548 ++++++++++++++++ package.json | 3 +- src/actions.ts | 28 +- src/formatters.test.ts | 4 +- src/formatters.ts | 18 +- 13 files changed, 16647 insertions(+), 80 deletions(-) create mode 100644 package-lock.json diff --git a/eslint.config.js b/eslint.config.js index 43cacfce..87fbf9fd 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -4,11 +4,14 @@ import pluginJs from '@eslint/js' import tseslint from 'typescript-eslint' import pluginVue from 'eslint-plugin-vue' import stylistic from '@stylistic/eslint-plugin' +// import pluginNuxt from 'eslint-plugin-nuxt' + export default [ { plugins: { - '@stylistic': stylistic + '@stylistic': stylistic, + // '@nuxtjs': pluginNuxt, }, rules: { '@stylistic/indent': ['error', 2], @@ -20,4 +23,6 @@ export default [ pluginJs.configs.recommended, ...tseslint.configs.recommended, ...pluginVue.configs['flat/essential'], -] \ No newline at end of file + // pluginNuxt.configs.recommended +] + diff --git a/nuxt-web/components/Button.vue b/nuxt-web/components/Button.vue index f60f93b9..873d7198 100644 --- a/nuxt-web/components/Button.vue +++ b/nuxt-web/components/Button.vue @@ -1,7 +1,7 @@ diff --git a/nuxt-web/components/content/formatters/FormatNumber.vue b/nuxt-web/components/content/formatters/FormatNumber.vue index 60e9eff8..2cd67d53 100644 --- a/nuxt-web/components/content/formatters/FormatNumber.vue +++ b/nuxt-web/components/content/formatters/FormatNumber.vue @@ -2,20 +2,20 @@ - + - + - {{ formatNumber(currency, { ...(decimals ? { decimals } : {}), locale }) }} + {{ formatNumber(currency, { ...(isNumber(decimals) ? { decimals } : {}), locale }) }} diff --git a/nuxt-web/components/content/formatters/FormatPercentage.vue b/nuxt-web/components/content/formatters/FormatPercentage.vue index de602ec5..e0cf6c85 100644 --- a/nuxt-web/components/content/formatters/FormatPercentage.vue +++ b/nuxt-web/components/content/formatters/FormatPercentage.vue @@ -2,20 +2,20 @@ - + - + - {{ formatPercentage(percentage, { decimals, locale }) }} + {{ formatPercentage(percentage, { ...(isNumber(decimals) ? { decimals } : {}), locale }) }} diff --git a/nuxt-web/components/content/formatters/FormatUnit.vue b/nuxt-web/components/content/formatters/FormatUnit.vue index fa8b3892..e30c1e1a 100644 --- a/nuxt-web/components/content/formatters/FormatUnit.vue +++ b/nuxt-web/components/content/formatters/FormatUnit.vue @@ -1,14 +1,14 @@ diff --git a/nuxt-web/components/example/Code.vue b/nuxt-web/components/example/Code.vue index ed99f300..40407370 100644 --- a/nuxt-web/components/example/Code.vue +++ b/nuxt-web/components/example/Code.vue @@ -1,19 +1,19 @@ diff --git a/nuxt-web/components/form/FormNumber.vue b/nuxt-web/components/form/FormNumber.vue index 776e05ae..d0eed89f 100644 --- a/nuxt-web/components/form/FormNumber.vue +++ b/nuxt-web/components/form/FormNumber.vue @@ -1,18 +1,22 @@