diff --git a/cli.js b/cli.js index b0d8a4b..35a3fa5 100755 --- a/cli.js +++ b/cli.js @@ -8,19 +8,6 @@ if (!arg) { process.exit(1); } const input = arg.toUpperCase(); -// try { -// console.log(input); -// console.log(Number(input)); -// const isValidRoman = isRoman(input); -// console.log(fromRoman(input)); -// console.log(isValidRoman); -// if (isValidRoman === true) { -// console.log(`${input} is a valid Roman numeral.`); -// } -// } catch (error) { -// console.error("Error: ", error); -// process.exit(1); -// } if ((0, index_1.isRoman)(input) === true) { console.log((0, index_1.fromRoman)(input)); } else { diff --git a/index.js b/index.js index 2b43b37..3204526 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,11 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.range = +exports.random = + exports.min = + exports.max = + exports.divide = + exports.multiply = + exports.range = exports.diff = exports.sum = exports.fromRoman = @@ -28,14 +33,15 @@ exports.getCount = getCount; * Confirm that string is a valid roman numeral * @param { string } value String to be tested * @returns { boolean } true or false + * @throws { Error } When the input is not a valid roman numeral */ function isRoman(value) { if (!value) { - return new Error("Roman numeral cannot be empty"); + throw new Error("Roman numeral cannot be empty"); } // Input must be a string and not be a number if (typeof value !== "string" || Number(value)) { - return new Error("Roman numeral must be of type string"); + throw new Error("Roman numeral must be of type string"); } value = value.toUpperCase(); const letters = value.split(""); @@ -54,7 +60,7 @@ function isRoman(value) { const [char, maxCount] = romans[i]; const count = getCount(letters, char); if (count && count > maxCount) { - return new Error( + throw new Error( `${char} cannot appear more than ${maxCount} times in a value` ); } @@ -63,7 +69,7 @@ function isRoman(value) { if (letters.length < 2) { let letter = letters[0]; if (!romanLetters.includes(letter)) { - return new Error(`Invalid Roman numeral: ${letter}`); + throw new Error(`Invalid Roman numeral: ${letter}`); } else { return true; } @@ -71,23 +77,26 @@ function isRoman(value) { // Correct letters letters.forEach((letter, index) => { if (!romanLetters.includes(letter)) { - return new Error(`Invalid Roman numeral: ${letter}`); + throw new Error(`Invalid Roman numeral: ${letter}`); } let next = letters[index + 1]; // Test for D if (letter === romanLetters[1]) { let badNexts = romanLetters.slice(0, 2); if (badNexts.includes(next)) { - return new Error( + throw new Error( `Unexpected token ${next}, ${next} cannot come after ${letter}` ); } } // Test for L if (letter === romanLetters[3]) { - let goodNexts = romanLetters.slice(4, 3); + let goodNexts = romanLetters.slice(4); + if (next === undefined) { + return; + } if (!goodNexts.includes(next)) { - return new Error( + throw new Error( `Unexpected token ${next}, expected either ${goodNexts[0]}, ${goodNexts[1]} or ${goodNexts[2]}` ); } @@ -96,7 +105,7 @@ function isRoman(value) { if (letter === romanLetters[4]) { let badNexts = romanLetters.slice(0, 2); if (badNexts.includes(next)) { - return new Error( + throw new Error( `Unexpected token ${next}, ${next} cannot come after ${letter}` ); } @@ -104,15 +113,21 @@ function isRoman(value) { // Test for V if (letter === romanLetters[5]) { let goodNexts = [romanLetters[6]]; + if (next === undefined) { + return; + } if (!goodNexts.includes(next)) { - return new Error(`Unexpected token ${next}, expected ${goodNexts[0]}`); + throw new Error(`Unexpected token ${next}, expected ${goodNexts[0]}`); } } // Test for I if (letter === romanLetters[6]) { - let goodNexts = romanLetters.slice(4, 3); + let goodNexts = romanLetters.slice(4); + if (next === undefined) { + return; + } if (!goodNexts.includes(next)) { - return new Error( + throw new Error( `Unexpected token ${next}, expected either ${goodNexts[0]}, ${goodNexts[1]} or ${goodNexts[2]}` ); } @@ -125,6 +140,7 @@ exports.isRoman = isRoman; * Convert an integer to Roman numerals * @param { number } value Integer to be converted to Roman numerals * @returns { string } Roman numeral representation of the input value + * @throws { Error } When the input is not a valid integer or is out of range */ function toRoman(value) { if (!Number.isInteger(value)) { @@ -162,6 +178,7 @@ exports.toRoman = toRoman; * Convert Roman numeral to integer * @param { string } value Roman numeral to be converted to integer * @returns { number } Integer representation of the input value + * @throws { Error } When the input is not a valid Roman numeral */ function fromRoman(value) { let arabNum = 0; @@ -218,6 +235,7 @@ exports.fromRoman = fromRoman; * @param expected { string } Expected response type * @param args { string[] } Roman numerals to be added * @returns { string | number } Final roman numeral + * @throws { Error } When the result exceeds maximum value of 3999 or invalid numeral is provided */ function sum(expected, ...args) { let sum = 0; @@ -234,11 +252,12 @@ exports.sum = sum; * @param expected { string } Expected response type * @param numerals { string[] } Roman numerals to subtract * @returns { string | number } + * @throws { Error } When more than two numerals are provided */ function diff(expected, numerals) { let sum = 0; if (numerals.length > 2) { - return new Error("Cannot subtract more than 2 numerals"); + throw new Error("Cannot subtract more than 2 numerals"); } if (isRoman(numerals[0]) && isRoman(numerals[1])) { sum = Math.abs(fromRoman(numerals[0]) - fromRoman(numerals[1])); @@ -251,6 +270,8 @@ exports.diff = diff; * @param end { string | number } Value to stop at * @param start { string | number } Value to start from * @param intervals { string | number } Difference between values + * @returns { string[] } Array of roman numerals in the specified range + * @throws { Error } When any of the inputs are invalid or out of range */ function range(end, start = "I", intervals = "I") { let endNum = 1; @@ -264,11 +285,11 @@ function range(end, start = "I", intervals = "I") { } } else if (typeof end === "number") { if (end >= 4000 || end <= 0) { - return new Error("Range has to be between 1 and 3999"); + throw new Error("Range has to be between 1 and 3999"); } endNum = end; } else { - return new Error("End value must be a string or number"); + throw new Error("End value must be a string or number"); } // Validate start value if (start && typeof start === "string") { @@ -277,11 +298,11 @@ function range(end, start = "I", intervals = "I") { } } else if (start && typeof start === "number") { if (start >= 4000 || start <= 0) { - return new Error("Range has to be between 1 and 3999"); + throw new Error("Range has to be between 1 and 3999"); } startNum = start; } else { - return new Error("Start value must be a string or number"); + throw new Error("Start value must be a string or number"); } // Validate interval value if (intervals && typeof intervals === "string") { @@ -290,11 +311,11 @@ function range(end, start = "I", intervals = "I") { } } else if (intervals && typeof intervals === "number") { if (intervals >= 4000 || intervals <= 0) { - return new Error("Range has to be between 1 and 3999"); + throw new Error("Range has to be between 1 and 3999"); } diffNum = intervals; } else { - return new Error("Start value must be a string or number"); + throw new Error("Start value must be a string or number"); } for (let i = startNum; i < endNum + 1; i += diffNum) { ranged.push(toRoman(i)); @@ -302,3 +323,120 @@ function range(end, start = "I", intervals = "I") { return ranged; } exports.range = range; +/** + * Multiply roman numerals + * @param expected { string } Expected response type + * @param args { string[] } Roman numerals to be added + * @returns { string | number } Final roman numeral + * @throws { Error } When the result exceeds maximum value of 3999 or invalid numeral is provided + */ +function multiply(expected, ...args) { + let product = 1; + for (let i = 0; i < args.length; i++) { + if (isRoman(args[i]) !== true) { + throw new Error(`Invalid Roman numeral: ${args[i]}`); + } + product *= fromRoman(args[i]); + if (product > 3999) { + throw new Error("Result exceeds maximum value of 3999"); + } + } + return expected === "number" ? product : toRoman(product); +} +exports.multiply = multiply; +/** + * Divide two roman numerals + * @param expected { string } Expected response type + * @param numerals { string[] } Roman numerals to divide + * @returns { string | number } + * @throws { Error } When more than two numerals are provided + */ +function divide(expected, numerals) { + let quotient = 0; + if (numerals.length > 2) { + throw new Error("Cannot divide more than 2 numerals"); + } + if (isRoman(numerals[0]) && isRoman(numerals[1])) { + quotient = Math.floor(fromRoman(numerals[0]) / fromRoman(numerals[1])); + } + return expected === "number" ? quotient : toRoman(quotient); +} +exports.divide = divide; +function max(...args) { + let maxNum = 0; + for (let i = 0; i < args.length; i++) { + let currentNum = args[i]; + if (isRoman(currentNum) !== true) { + throw new Error(`Invalid Roman numeral: ${args[i]}`); + } + let currentRomanNum = fromRoman(currentNum); + if (currentRomanNum > maxNum) { + maxNum = currentRomanNum; + } + } + return toRoman(maxNum); +} +exports.max = max; +function min(...args) { + let minNum = 4000; + for (let i = 0; i < args.length; i++) { + let currentNum = args[i]; + if (isRoman(currentNum) !== true) { + throw new Error(`Invalid Roman numeral: ${args[i]}`); + } + let currentRomanNum = fromRoman(currentNum); + if (currentRomanNum < minNum) { + minNum = currentRomanNum; + } + } + return toRoman(minNum); +} +exports.min = min; +/** + * Generate a random Roman numeral within a specified range + * @param max Maximum value + * @param min Minimum value + * @returns { string } Random Roman numeral within the specified range + * @throws { Error } When the inputs are invalid or out of range + */ +function random(max = 3999, min = 1) { + let maxNum = 3999; + let minNum = 1; + if (typeof max === "number") { + maxNum = max; + if (maxNum > 3999 || maxNum <= 0) { + throw new Error("Max value must be between 1 and 3999"); + } + } else if (typeof max === "string") { + if (isRoman(max)) { + maxNum = fromRoman(max); + if (maxNum > 3999 || maxNum <= 0) { + throw new Error("Max value must be between 1 and 3999"); + } + } + } else { + throw new Error("Max value must be a number or string"); + } + if (typeof min === "number") { + minNum = min; + if (minNum >= maxNum || minNum <= 0) { + throw new Error( + "Min value must be less than max value and greater than 0" + ); + } + } else if (typeof min === "string") { + if (isRoman(min)) { + minNum = fromRoman(min); + if (minNum >= maxNum || minNum <= 0) { + throw new Error( + "Min value must be less than max value and greater than 0" + ); + } + } + } else { + throw new Error("Min value must be a number or string"); + } + const randomNum = Math.floor(Math.random() * (maxNum - minNum + 1)) + minNum; + return toRoman(randomNum); +} +exports.random = random; diff --git a/src/index.ts b/src/index.ts index 5dae3db..9281c73 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,7 +6,10 @@ type general = string | number; * @param { string | number } value string or number to be counted * @return { number } Count of value in an array */ -export function getCount(array: string[], value: general): number { +export function getCount( + array: string[], + value: general +): number { let count: number = 0; array.forEach((item) => { @@ -22,15 +25,16 @@ export function getCount(array: string[], value: general): number { * Confirm that string is a valid roman numeral * @param { string } value String to be tested * @returns { boolean } true or false + * @throws { Error } When the input is not a valid roman numeral */ -export function isRoman(value: string): true | Error { +export function isRoman(value: string): true { if (!value) { - return new Error("Roman numeral cannot be empty"); + throw new Error("Roman numeral cannot be empty"); } // Input must be a string and not be a number if (typeof value !== "string" || Number(value)) { - return new Error("Roman numeral must be of type string"); + throw new Error("Roman numeral must be of type string"); } value = value.toUpperCase(); @@ -52,7 +56,7 @@ export function isRoman(value: string): true | Error { const [char, maxCount] = romans[i]; const count = getCount(letters, char); if (count && count > maxCount) { - return new Error(`${char} cannot appear more than ${maxCount} times in a value`); + throw new Error(`${char} cannot appear more than ${maxCount} times in a value`); } } @@ -61,7 +65,7 @@ export function isRoman(value: string): true | Error { let letter = letters[0]; if (!romanLetters.includes(letter)) { - return new Error(`Invalid Roman numeral: ${letter}`); + throw new Error(`Invalid Roman numeral: ${letter}`); } else { return true; } @@ -70,7 +74,7 @@ export function isRoman(value: string): true | Error { // Correct letters letters.forEach((letter, index) => { if (!romanLetters.includes(letter)) { - return new Error(`Invalid Roman numeral: ${letter}`); + throw new Error(`Invalid Roman numeral: ${letter}`); } let next = letters[index + 1]; @@ -80,7 +84,7 @@ export function isRoman(value: string): true | Error { let badNexts = romanLetters.slice(0, 2); if (badNexts.includes(next)) { - return new Error( + throw new Error( `Unexpected token ${next}, ${next} cannot come after ${letter}` ); } @@ -88,10 +92,14 @@ export function isRoman(value: string): true | Error { // Test for L if (letter === romanLetters[3]) { - let goodNexts = romanLetters.slice(4, 3); + let goodNexts = romanLetters.slice(4); + + if (next === undefined) { + return; + } if (!goodNexts.includes(next)) { - return new Error( + throw new Error( `Unexpected token ${next}, expected either ${goodNexts[0]}, ${goodNexts[1]} or ${goodNexts[2]}` ); } @@ -102,7 +110,7 @@ export function isRoman(value: string): true | Error { let badNexts = romanLetters.slice(0, 2); if (badNexts.includes(next)) { - return new Error( + throw new Error( `Unexpected token ${next}, ${next} cannot come after ${letter}` ); } @@ -112,17 +120,25 @@ export function isRoman(value: string): true | Error { if (letter === romanLetters[5]) { let goodNexts = [romanLetters[6]]; + if (next === undefined) { + return; + } + if (!goodNexts.includes(next)) { - return new Error(`Unexpected token ${next}, expected ${goodNexts[0]}`); + throw new Error(`Unexpected token ${next}, expected ${goodNexts[0]}`); } } // Test for I if (letter === romanLetters[6]) { - let goodNexts = romanLetters.slice(4, 3); + let goodNexts = romanLetters.slice(4); + + if (next === undefined) { + return; + } if (!goodNexts.includes(next)) { - return new Error( + throw new Error( `Unexpected token ${next}, expected either ${goodNexts[0]}, ${goodNexts[1]} or ${goodNexts[2]}` ); } @@ -136,6 +152,7 @@ export function isRoman(value: string): true | Error { * Convert an integer to Roman numerals * @param { number } value Integer to be converted to Roman numerals * @returns { string } Roman numeral representation of the input value + * @throws { Error } When the input is not a valid integer or is out of range */ export function toRoman(value: number): string { if (!Number.isInteger(value)) { @@ -177,8 +194,9 @@ export function toRoman(value: number): string { * Convert Roman numeral to integer * @param { string } value Roman numeral to be converted to integer * @returns { number } Integer representation of the input value + * @throws { Error } When the input is not a valid Roman numeral */ -export function fromRoman(value: string): number | Error { +export function fromRoman(value: string): number { let arabNum: number = 0; if (isRoman(value)) { @@ -237,11 +255,12 @@ export function fromRoman(value: string): number | Error { * @param expected { string } Expected response type * @param args { string[] } Roman numerals to be added * @returns { string | number } Final roman numeral + * @throws { Error } When the result exceeds maximum value of 3999 or invalid numeral is provided */ export function sum( expected: "number" | "roman", ...args: string[] -): general | Error { +): general { let sum = 0; args.forEach((numeral) => { @@ -258,15 +277,16 @@ export function sum( * @param expected { string } Expected response type * @param numerals { string[] } Roman numerals to subtract * @returns { string | number } + * @throws { Error } When more than two numerals are provided */ export function diff( expected: "number" | "roman", numerals: [string, string] -): general | Error { +): general { let sum = 0; if (numerals.length > 2) { - return new Error("Cannot subtract more than 2 numerals"); + throw new Error("Cannot subtract more than 2 numerals"); } if (isRoman(numerals[0]) && isRoman(numerals[1])) { @@ -283,12 +303,14 @@ export function diff( * @param end { string | number } Value to stop at * @param start { string | number } Value to start from * @param intervals { string | number } Difference between values + * @returns { string[] } Array of roman numerals in the specified range + * @throws { Error } When any of the inputs are invalid or out of range */ export function range( end: general, start: general = "I", intervals: general = "I" -): string[] | Error { +): string[] { let endNum: number = 1; let startNum: number = 1; let diffNum: number = 1; @@ -301,12 +323,12 @@ export function range( } } else if (typeof end === "number") { if (end >= 4000 || end <= 0) { - return new Error("Range has to be between 1 and 3999"); + throw new Error("Range has to be between 1 and 3999"); } endNum = end; } else { - return new Error("End value must be a string or number"); + throw new Error("End value must be a string or number"); } // Validate start value @@ -316,12 +338,12 @@ export function range( } } else if (start && typeof start === "number") { if (start >= 4000 || start <= 0) { - return new Error("Range has to be between 1 and 3999"); + throw new Error("Range has to be between 1 and 3999"); } startNum = start; } else { - return new Error("Start value must be a string or number"); + throw new Error("Start value must be a string or number"); } // Validate interval value @@ -331,12 +353,12 @@ export function range( } } else if (intervals && typeof intervals === "number") { if (intervals >= 4000 || intervals <= 0) { - return new Error("Range has to be between 1 and 3999"); + throw new Error("Range has to be between 1 and 3999"); } diffNum = intervals; } else { - return new Error("Start value must be a string or number"); + throw new Error("Start value must be a string or number"); } for (let i = startNum; i < endNum + 1; i += diffNum) { @@ -345,3 +367,152 @@ export function range( return ranged; } + +/** + * Multiply roman numerals + * @param expected { string } Expected response type + * @param args { string[] } Roman numerals to be added + * @returns { string | number } Final roman numeral + * @throws { Error } When the result exceeds maximum value of 3999 or invalid numeral is provided + */ +export function multiply( + expected: "number" | "roman", + ...args: string[] +): general { + let product = 1; + + for (let i = 0; i < args.length; i++) { + if (isRoman(args[i]) !== true) { + throw new Error(`Invalid Roman numeral: ${args[i]}`); + } + + product *= fromRoman(args[i]) as number; + + if (product > 3999) { + throw new Error("Result exceeds maximum value of 3999"); + } + } + + return expected === "number" ? product : toRoman(product); +} + +/** + * Divide two roman numerals + * @param expected { string } Expected response type + * @param numerals { string[] } Roman numerals to divide + * @returns { string | number } + * @throws { Error } When more than two numerals are provided + */ +export function divide( + expected: "number" | "roman", + numerals: [string, string] +): general { + let quotient = 0; + + if (numerals.length > 2) { + throw new Error("Cannot divide more than 2 numerals"); + } + + if (isRoman(numerals[0]) && isRoman(numerals[1])) { + quotient = Math.floor( + (fromRoman(numerals[0]) as number) / (fromRoman(numerals[1]) as number) + ); + } + + return expected === "number" ? quotient : toRoman(quotient); +} + +export function max(...args: string[]): string { + let maxNum: number = 0; + + for (let i = 0; i < args.length; i++) { + let currentNum = args[i]; + + if (isRoman(currentNum) !== true) { + throw new Error(`Invalid Roman numeral: ${args[i]}`); + } + + let currentRomanNum = fromRoman(currentNum) as number; + + if (currentRomanNum > maxNum) { + maxNum = currentRomanNum; + } + } + + return toRoman(maxNum); +} + +export function min(...args: string[]): string { + let minNum: number = 4000; + + for (let i = 0; i < args.length; i++) { + let currentNum = args[i]; + + if (isRoman(currentNum) !== true) { + throw new Error(`Invalid Roman numeral: ${args[i]}`); + } + + let currentRomanNum = fromRoman(currentNum) as number; + + if (currentRomanNum < minNum) { + minNum = currentRomanNum; + } + } + + return toRoman(minNum); +} + +/** + * Generate a random Roman numeral within a specified range + * @param max Maximum value + * @param min Minimum value + * @returns { string } Random Roman numeral within the specified range + * @throws { Error } When the inputs are invalid or out of range + */ +export function random( + max: general = 3999, + min: general = 1, +): string { + let maxNum: number = 3999; + let minNum: number = 1; + + if (typeof max === "number") { + maxNum = max; + + if (maxNum > 3999 || maxNum <= 0) { + throw new Error("Max value must be between 1 and 3999"); + } + } else if (typeof max === "string") { + if (isRoman(max)) { + maxNum = fromRoman(max) as number; + + if (maxNum > 3999 || maxNum <= 0) { + throw new Error("Max value must be between 1 and 3999"); + } + } + } else { + throw new Error("Max value must be a number or string"); + } + + if (typeof min === "number") { + minNum = min; + + if (minNum >= maxNum || minNum <= 0) { + throw new Error("Min value must be less than max value and greater than 0"); + } + } else if (typeof min === "string") { + if (isRoman(min)) { + minNum = fromRoman(min) as number; + + if (minNum >= maxNum || minNum <= 0) { + throw new Error("Min value must be less than max value and greater than 0"); + } + } + } else { + throw new Error("Min value must be a number or string"); + } + + const randomNum = Math.floor(Math.random() * (maxNum - minNum + 1)) + minNum; + + return toRoman(randomNum); +} diff --git a/src/tests/index.test.ts b/src/tests/index.test.ts index 1479c99..f33d376 100644 --- a/src/tests/index.test.ts +++ b/src/tests/index.test.ts @@ -1,11 +1,15 @@ import { diff, + divide, fromRoman, getCount, isRoman, + multiply, range, sum, toRoman, + max, + min, random } from "../index"; describe("getCount", () => { @@ -64,7 +68,7 @@ describe("isRoman", () => { } catch (error) { // @ts-ignore expect(error.message).toBe( - "I cannot appear more than 3 times in a value" + "V cannot appear more than 1 times in a value" ); } }); @@ -83,7 +87,7 @@ describe("isRoman", () => { isRoman("XVY"); } catch (error) { // @ts-ignore - expect(error.message).toBe("Invalid Roman numeral: Y"); + expect(error.message).toBe("Unexpected token Y, expected I"); } }); @@ -121,7 +125,7 @@ describe("isRoman", () => { isRoman("VM"); } catch (error) { // @ts-ignore - expect(error.message).toBe("Unexpected token M, M cannot come after V"); + expect(error.message).toBe("Unexpected token M, expected I"); } }); @@ -130,7 +134,7 @@ describe("isRoman", () => { isRoman("IM"); } catch (error) { // @ts-ignore - expect(error.message).toBe("Unexpected token M, M cannot come after I"); + expect(error.message).toBe("Unexpected token M, expected either X, V or I"); } }); @@ -250,3 +254,252 @@ describe("range", () => { expect(sample).toEqual(["C", "CL", "CC", "CCL"]); }); }); + +describe("multiply", () => { + test("should error on any invalid input", () => { + try { + // @ts-ignore + sum("number", "X", 5); + } catch (error) { + // @ts-ignore + expect(error.message).toBe( + "Roman numeral must be of type string" + ); + } + }); + + const testCases1: [string, string, string][] = [ + ["L", "X", "V"], + ["CC", "XX", "X"], + ["D", "L", "X"], + ["MMDCCCLXXX", "CXX", "XXIV"], + ]; + + test.each(testCases1)( + "should return %s when called with %s and %s", + (expected, roman1, roman2) => { + expect(multiply("roman", roman1, roman2)).toBe(expected); + } + ); + + const testCases2: [number, string, string][] = [ + [50, "X", "V"], + [200, "XX", "X"], + [500, "L", "X"], + [2880, "CXX", "XXIV"], + ]; + + test.each(testCases2)( + "should return %s when called with %s and %s", + (expected, roman1, roman2) => { + expect(multiply("number", roman1, roman2)).toBe(expected); + } + ); + + test("should throw an error when result exceeds 4000 in roman mode", () => { + try { + multiply("roman", "MM", "III"); + } catch (error) { + // @ts-ignore + expect(error.message).toBe( + "Result exceeds maximum value of 3999" + ); + } + }); +}); + +describe("divide", () => { + test("should error on any invalid input", () => { + try { + // @ts-ignore + sum("number", "X", 5); + } catch (error) { + // @ts-ignore + expect(error.message).toBe( + "Roman numeral must be of type string" + ); + } + }); + + const testCases1: [string, string, string][] = [ + ["II", "X", "V"], + ["II", "XX", "X"], + ["V", "L", "X"], + ["V", "CXX", "XXIV"], + ]; + + test.each(testCases1)( + "should return %s when called with %s and %s", + (expected, roman1, roman2) => { + expect(divide("roman", [roman1, roman2])).toBe(expected); + } + ); + + const testCases2: [number, string, string][] = [ + [2, "X", "V"], + [2, "XX", "X"], + [5, "L", "X"], + [5, "CXX", "XXIV"], + ]; + + test.each(testCases2)( + "should return %s when called with %s and %s", + (expected, roman1, roman2) => { + expect(divide("number", [roman1, roman2])).toBe(expected); + } + ); +}); + +describe("max", () => { + test("should throw an error on invalid input", () => { + try { + // @ts-ignore + expect(max("X", 5)); + } catch (error) { + // @ts-ignore + expect(error.message).toBe( + "Roman numeral must be of type string" + ); + } + }); + + const testCases: [string, string[]][] = [ + ["X", ["X", "V", "III", "VIII"]], + ["MM", ["MM", "MCMXC", "MDCCCLXXXVIII", "MCMLXXVI"]], + ["CDXLIV", ["CDXLIV", "CCCXL", "CCCLX", "CDXX"]], + ]; + + test.each(testCases)( + "should return the maximum value from %s", + (expected, inputs) => { + expect(max(...inputs)).toBe(expected); + } + ); +}); + +describe("min", () => { + test("should throw an error on invalid input", () => { + try { + // @ts-ignore + expect(min("X", 5)); + } catch (error) { + // @ts-ignore + expect(error.message).toBe( + "Roman numeral must be of type string" + ); + } + }); + + const testCases: [string, string[]][] = [ + ["III", ["X", "V", "III", "VIII"]], + ["MDCCCLXXXVIII", ["MM", "MCMXC", "MDCCCLXXXVIII", "MCMLXXVI"]], + ["CCCXL", ["CDXLIV", "CCCXL", "CCCLX", "CDXX"]], + ]; + + test.each(testCases)( + "should return the maximum value from %s", + (expected, inputs) => { + expect(min(...inputs)).toBe(expected); + } + ); +}); + +describe("random", () => { + test("should throw error on invalid max", () => { + try { + // @ts-ignore + random([43]); + } catch (error) { + // @ts-ignore + expect(error.message).toBe("Max value must be a number or string"); + } + }); + + test("should throw error on invalid max (number)", () => { + try { + // @ts-ignore + random(4897); + } catch (error) { + // @ts-ignore + expect(error.message).toBe("Max value must be between 1 and 3999"); + } + }); + + test("should throw error on invalid max (roman)", () => { + try { + // @ts-ignore + random("MMMM"); + } catch (error) { + // @ts-ignore + expect(error.message).toBe("Max value must be between 1 and 3999"); + } + }); + + test("should throw error on invalid max (roman) II", () => { + try { + // @ts-ignore + random("VIJ"); + } catch (error) { + // @ts-ignore + expect(error.message).toBe("Unexpected token J, expected either X, V or I"); + } + }); + + test("should throw error on invalid min", () => { + try { + // @ts-ignore + random(100, {}); + } catch (error) { + // @ts-ignore + expect(error.message).toBe("Min value must be a number or string"); + } + }); + + test("should throw error on invalid min (number)", () => { + try { + // @ts-ignore + random(300, -23); + } catch (error) { + // @ts-ignore + expect(error.message).toBe("Min value must be less than max value and greater than 0"); + } + }); + + test("should throw error on invalid min (roman)", () => { + try { + // @ts-ignore + random(300, "MMMM"); + } catch (error) { + // @ts-ignore + expect(error.message).toBe("Min value must be less than max value and greater than 0"); + } + }); + + test("should throw error on invalid min (roman) II", () => { + try { + // @ts-ignore + random(230, "VIJ"); + } catch (error) { + // @ts-ignore + expect(error.message).toBe("Unexpected token J, expected either X, V or I"); + } + }); + + test("should return a number between 1 and 100", () => { + const result = random(100); + expect(fromRoman(result)).toBeGreaterThanOrEqual(1); + expect(fromRoman(result)).toBeLessThanOrEqual(100); + }); + + test("should return a number between 50 and 150", () => { + const result = random(150, 50); + expect(fromRoman(result)).toBeGreaterThanOrEqual(50); + expect(fromRoman(result)).toBeLessThanOrEqual(150); + }); + + test("should return a number between 1 and 3999", () => { + const result = random(); + expect(fromRoman(result)).toBeGreaterThanOrEqual(1); + expect(fromRoman(result)).toBeLessThanOrEqual(3999); + }); +}); diff --git a/tests/index.test.js b/tests/index.test.js index 49e7be8..fd21976 100644 --- a/tests/index.test.js +++ b/tests/index.test.js @@ -51,7 +51,7 @@ describe("isRoman", () => { } catch (error) { // @ts-ignore expect(error.message).toBe( - "I cannot appear more than 3 times in a value" + "V cannot appear more than 1 times in a value" ); } }); @@ -68,7 +68,7 @@ describe("isRoman", () => { (0, index_1.isRoman)("XVY"); } catch (error) { // @ts-ignore - expect(error.message).toBe("Invalid Roman numeral: Y"); + expect(error.message).toBe("Unexpected token Y, expected I"); } }); test("should throw an error if D is followed by M", () => { @@ -102,7 +102,7 @@ describe("isRoman", () => { (0, index_1.isRoman)("VM"); } catch (error) { // @ts-ignore - expect(error.message).toBe("Unexpected token M, M cannot come after V"); + expect(error.message).toBe("Unexpected token M, expected I"); } }); test("should throw an error if I is followed by M", () => { @@ -110,7 +110,9 @@ describe("isRoman", () => { (0, index_1.isRoman)("IM"); } catch (error) { // @ts-ignore - expect(error.message).toBe("Unexpected token M, M cannot come after I"); + expect(error.message).toBe( + "Unexpected token M, expected either X, V or I" + ); } }); test("should return true on good input", () => { @@ -212,3 +214,222 @@ describe("range", () => { expect(sample).toEqual(["C", "CL", "CC", "CCL"]); }); }); +describe("multiply", () => { + test("should error on any invalid input", () => { + try { + // @ts-ignore + (0, index_1.sum)("number", "X", 5); + } catch (error) { + // @ts-ignore + expect(error.message).toBe("Roman numeral must be of type string"); + } + }); + const testCases1 = [ + ["L", "X", "V"], + ["CC", "XX", "X"], + ["D", "L", "X"], + ["MMDCCCLXXX", "CXX", "XXIV"], + ]; + test.each(testCases1)( + "should return %s when called with %s and %s", + (expected, roman1, roman2) => { + expect((0, index_1.multiply)("roman", roman1, roman2)).toBe(expected); + } + ); + const testCases2 = [ + [50, "X", "V"], + [200, "XX", "X"], + [500, "L", "X"], + [2880, "CXX", "XXIV"], + ]; + test.each(testCases2)( + "should return %s when called with %s and %s", + (expected, roman1, roman2) => { + expect((0, index_1.multiply)("number", roman1, roman2)).toBe(expected); + } + ); + test("should throw an error when result exceeds 4000 in roman mode", () => { + try { + (0, index_1.multiply)("roman", "MM", "III"); + } catch (error) { + // @ts-ignore + expect(error.message).toBe("Result exceeds maximum value of 3999"); + } + }); +}); +describe("divide", () => { + test("should error on any invalid input", () => { + try { + // @ts-ignore + (0, index_1.sum)("number", "X", 5); + } catch (error) { + // @ts-ignore + expect(error.message).toBe("Roman numeral must be of type string"); + } + }); + const testCases1 = [ + ["II", "X", "V"], + ["II", "XX", "X"], + ["V", "L", "X"], + ["V", "CXX", "XXIV"], + ]; + test.each(testCases1)( + "should return %s when called with %s and %s", + (expected, roman1, roman2) => { + expect((0, index_1.divide)("roman", [roman1, roman2])).toBe(expected); + } + ); + const testCases2 = [ + [2, "X", "V"], + [2, "XX", "X"], + [5, "L", "X"], + [5, "CXX", "XXIV"], + ]; + test.each(testCases2)( + "should return %s when called with %s and %s", + (expected, roman1, roman2) => { + expect((0, index_1.divide)("number", [roman1, roman2])).toBe(expected); + } + ); +}); +describe("max", () => { + test("should throw an error on invalid input", () => { + try { + // @ts-ignore + expect((0, index_1.max)("X", 5)); + } catch (error) { + // @ts-ignore + expect(error.message).toBe("Roman numeral must be of type string"); + } + }); + const testCases = [ + ["X", ["X", "V", "III", "VIII"]], + ["MM", ["MM", "MCMXC", "MDCCCLXXXVIII", "MCMLXXVI"]], + ["CDXLIV", ["CDXLIV", "CCCXL", "CCCLX", "CDXX"]], + ]; + test.each(testCases)( + "should return the maximum value from %s", + (expected, inputs) => { + expect((0, index_1.max)(...inputs)).toBe(expected); + } + ); +}); +describe("min", () => { + test("should throw an error on invalid input", () => { + try { + // @ts-ignore + expect((0, index_1.min)("X", 5)); + } catch (error) { + // @ts-ignore + expect(error.message).toBe("Roman numeral must be of type string"); + } + }); + const testCases = [ + ["III", ["X", "V", "III", "VIII"]], + ["MDCCCLXXXVIII", ["MM", "MCMXC", "MDCCCLXXXVIII", "MCMLXXVI"]], + ["CCCXL", ["CDXLIV", "CCCXL", "CCCLX", "CDXX"]], + ]; + test.each(testCases)( + "should return the maximum value from %s", + (expected, inputs) => { + expect((0, index_1.min)(...inputs)).toBe(expected); + } + ); +}); +describe("random", () => { + test("should throw error on invalid max", () => { + try { + // @ts-ignore + (0, index_1.random)([43]); + } catch (error) { + // @ts-ignore + expect(error.message).toBe("Max value must be a number or string"); + } + }); + test("should throw error on invalid max (number)", () => { + try { + // @ts-ignore + (0, index_1.random)(4897); + } catch (error) { + // @ts-ignore + expect(error.message).toBe("Max value must be between 1 and 3999"); + } + }); + test("should throw error on invalid max (roman)", () => { + try { + // @ts-ignore + (0, index_1.random)("MMMM"); + } catch (error) { + // @ts-ignore + expect(error.message).toBe("Max value must be between 1 and 3999"); + } + }); + test("should throw error on invalid max (roman) II", () => { + try { + // @ts-ignore + (0, index_1.random)("VIJ"); + } catch (error) { + // @ts-ignore + expect(error.message).toBe( + "Unexpected token J, expected either X, V or I" + ); + } + }); + test("should throw error on invalid min", () => { + try { + // @ts-ignore + (0, index_1.random)(100, {}); + } catch (error) { + // @ts-ignore + expect(error.message).toBe("Min value must be a number or string"); + } + }); + test("should throw error on invalid min (number)", () => { + try { + // @ts-ignore + (0, index_1.random)(300, -23); + } catch (error) { + // @ts-ignore + expect(error.message).toBe( + "Min value must be less than max value and greater than 0" + ); + } + }); + test("should throw error on invalid min (roman)", () => { + try { + // @ts-ignore + (0, index_1.random)(300, "MMMM"); + } catch (error) { + // @ts-ignore + expect(error.message).toBe( + "Min value must be less than max value and greater than 0" + ); + } + }); + test("should throw error on invalid min (roman) II", () => { + try { + // @ts-ignore + (0, index_1.random)(230, "VIJ"); + } catch (error) { + // @ts-ignore + expect(error.message).toBe( + "Unexpected token J, expected either X, V or I" + ); + } + }); + test("should return a number between 1 and 100", () => { + const result = (0, index_1.random)(100); + expect((0, index_1.fromRoman)(result)).toBeGreaterThanOrEqual(1); + expect((0, index_1.fromRoman)(result)).toBeLessThanOrEqual(100); + }); + test("should return a number between 50 and 150", () => { + const result = (0, index_1.random)(150, 50); + expect((0, index_1.fromRoman)(result)).toBeGreaterThanOrEqual(50); + expect((0, index_1.fromRoman)(result)).toBeLessThanOrEqual(150); + }); + test("should return a number between 1 and 3999", () => { + const result = (0, index_1.random)(); + expect((0, index_1.fromRoman)(result)).toBeGreaterThanOrEqual(1); + expect((0, index_1.fromRoman)(result)).toBeLessThanOrEqual(3999); + }); +});