From e3a9baf3cc7449664f7794305acca25b4ffc0b09 Mon Sep 17 00:00:00 2001 From: Zubair Idris Aweda Date: Sun, 19 Oct 2025 12:50:14 +0100 Subject: [PATCH 1/5] feat: multiply && chore: error handling --- cli.js | 13 ------ index.js | 83 ++++++++++++++++++++++++++--------- src/index.ts | 95 ++++++++++++++++++++++++++++++++--------- src/tests/index.test.ts | 63 ++++++++++++++++++++++++--- tests/index.test.js | 53 +++++++++++++++++++++-- 5 files changed, 245 insertions(+), 62 deletions(-) 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..5e4040f 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,11 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.range = +exports.min = + exports.max = + exports.average = + exports.divide = + exports.multiply = + exports.range = exports.diff = exports.sum = exports.fromRoman = @@ -31,11 +36,11 @@ exports.getCount = getCount; */ 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 +59,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 +68,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 +76,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 +104,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 +112,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]}` ); } @@ -238,7 +252,7 @@ exports.sum = sum; 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])); @@ -264,11 +278,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 +291,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 +304,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 +316,32 @@ 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; +function divide() {} +exports.divide = divide; +function average() {} +exports.average = average; +function max() {} +exports.max = max; +function min() {} +exports.min = min; diff --git a/src/index.ts b/src/index.ts index 5dae3db..746eadc 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) => { @@ -25,12 +28,12 @@ export function getCount(array: string[], value: general): number { */ export function isRoman(value: string): true | Error { 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 +55,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 +64,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 +73,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 +83,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 +91,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 +109,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 +119,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]}` ); } @@ -266,7 +281,7 @@ export function diff( 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])) { @@ -301,12 +316,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 +331,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 +346,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 +360,43 @@ 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 | Error { + 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); +} + +export function divide() { +} + +export function average() { +} + +export function max() { +} + +export function min() { +} diff --git a/src/tests/index.test.ts b/src/tests/index.test.ts index 1479c99..5623885 100644 --- a/src/tests/index.test.ts +++ b/src/tests/index.test.ts @@ -2,7 +2,7 @@ import { diff, fromRoman, getCount, - isRoman, + isRoman, multiply, range, sum, toRoman, @@ -64,7 +64,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 +83,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 +121,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 +130,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 +250,56 @@ 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" + ); + } + }); +}); diff --git a/tests/index.test.js b/tests/index.test.js index 49e7be8..22e03dc 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,46 @@ 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"); + } + }); +}); From 7f95de3ca7292252abd36d136b0b3bf85c4c4059 Mon Sep 17 00:00:00 2001 From: Zubair Idris Aweda Date: Sun, 19 Oct 2025 13:18:14 +0100 Subject: [PATCH 2/5] feat: divide --- index.js | 36 ++++++++++++++++++++++++----- src/index.ts | 51 ++++++++++++++++++++++++++++++++--------- src/tests/index.test.ts | 44 ++++++++++++++++++++++++++++++++++- tests/index.test.js | 35 ++++++++++++++++++++++++++++ 4 files changed, 148 insertions(+), 18 deletions(-) diff --git a/index.js b/index.js index 5e4040f..b4d34f8 100644 --- a/index.js +++ b/index.js @@ -2,7 +2,6 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.min = exports.max = - exports.average = exports.divide = exports.multiply = exports.range = @@ -33,6 +32,7 @@ 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) { @@ -139,6 +139,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)) { @@ -176,6 +177,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; @@ -232,6 +234,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; @@ -248,6 +251,7 @@ 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; @@ -265,6 +269,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; @@ -337,11 +343,29 @@ function multiply(expected, ...args) { return expected === "number" ? product : toRoman(product); } exports.multiply = multiply; -function divide() {} +/** + * 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 average() {} -exports.average = average; -function max() {} +function max(...args) { + return ""; +} exports.max = max; -function min() {} +function min(...args) { + return ""; +} exports.min = min; diff --git a/src/index.ts b/src/index.ts index 746eadc..89d2a4f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -25,8 +25,9 @@ export function 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 */ -export function isRoman(value: string): true | Error { +export function isRoman(value: string): true { if (!value) { throw new Error("Roman numeral cannot be empty"); } @@ -151,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)) { @@ -192,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)) { @@ -252,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) => { @@ -273,11 +277,12 @@ 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) { @@ -298,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; @@ -371,7 +378,7 @@ export function range( export function multiply( expected: "number" | "roman", ...args: string[] -): general | Error { +): general { let product = 1; for (let i = 0; i < args.length; i++) { @@ -389,14 +396,36 @@ export function multiply( return expected === "number" ? product : toRoman(product); } -export function divide() { -} +/** + * 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) + ); + } -export function average() { + return expected === "number" ? quotient : toRoman(quotient); } -export function max() { +export function max(...args: string[]): string { + return ""; } -export function min() { +export function min(...args: string[]): string { + return ""; } diff --git a/src/tests/index.test.ts b/src/tests/index.test.ts index 5623885..243e4f7 100644 --- a/src/tests/index.test.ts +++ b/src/tests/index.test.ts @@ -1,5 +1,5 @@ import { - diff, + diff, divide, fromRoman, getCount, isRoman, multiply, @@ -303,3 +303,45 @@ describe("multiply", () => { } }); }); + +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); + } + ); +}); diff --git a/tests/index.test.js b/tests/index.test.js index 22e03dc..92736ce 100644 --- a/tests/index.test.js +++ b/tests/index.test.js @@ -257,3 +257,38 @@ describe("multiply", () => { } }); }); +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); + } + ); +}); From ea0bde182d9e49e3c6a3b5621db852206e418c88 Mon Sep 17 00:00:00 2001 From: Zubair Idris Aweda Date: Sun, 19 Oct 2025 13:29:04 +0100 Subject: [PATCH 3/5] feat: min && max --- index.js | 26 +++++++++++++++-- src/index.ts | 36 ++++++++++++++++++++++-- src/tests/index.test.ts | 62 +++++++++++++++++++++++++++++++++++++++-- tests/index.test.js | 44 +++++++++++++++++++++++++++++ 4 files changed, 162 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index b4d34f8..50afd7e 100644 --- a/index.js +++ b/index.js @@ -362,10 +362,32 @@ function divide(expected, numerals) { } exports.divide = divide; function max(...args) { - return ""; + 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) { - return ""; + 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; diff --git a/src/index.ts b/src/index.ts index 89d2a4f..5bbda1f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -423,9 +423,41 @@ export function divide( } export function max(...args: string[]): string { - return ""; + 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 { - return ""; + 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); } diff --git a/src/tests/index.test.ts b/src/tests/index.test.ts index 243e4f7..ac99f80 100644 --- a/src/tests/index.test.ts +++ b/src/tests/index.test.ts @@ -1,11 +1,15 @@ import { - diff, divide, + diff, + divide, fromRoman, getCount, - isRoman, multiply, + isRoman, + multiply, range, sum, toRoman, + max, + min } from "../index"; describe("getCount", () => { @@ -345,3 +349,57 @@ describe("divide", () => { } ); }); + +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); + } + ); +}); diff --git a/tests/index.test.js b/tests/index.test.js index 92736ce..092c2a6 100644 --- a/tests/index.test.js +++ b/tests/index.test.js @@ -292,3 +292,47 @@ describe("divide", () => { } ); }); +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); + } + ); +}); From a2a0564bc2a4304b0fe8c5b2f500d6f41d4c1cf8 Mon Sep 17 00:00:00 2001 From: Zubair Idris Aweda Date: Sun, 19 Oct 2025 13:50:24 +0100 Subject: [PATCH 4/5] feat: random --- index.js | 51 ++++++++++++++++++- src/index.ts | 55 +++++++++++++++++++++ src/tests/index.test.ts | 107 +++++++++++++++++++++++++++++++++++++++- tests/index.test.js | 101 +++++++++++++++++++++++++++++++++++++ 4 files changed, 312 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 50afd7e..3204526 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,7 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.min = +exports.random = + exports.min = exports.max = exports.divide = exports.multiply = @@ -391,3 +392,51 @@ function min(...args) { 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 5bbda1f..9281c73 100644 --- a/src/index.ts +++ b/src/index.ts @@ -461,3 +461,58 @@ export function min(...args: string[]): string { 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 ac99f80..3d4b573 100644 --- a/src/tests/index.test.ts +++ b/src/tests/index.test.ts @@ -9,7 +9,7 @@ import { sum, toRoman, max, - min + min, random } from "../index"; describe("getCount", () => { @@ -403,3 +403,108 @@ describe("min", () => { } ); }); + +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); + }); + + test("should return L", () => { + const result = random(50, 49); + expect(result).toBe("L"); + }); +}); diff --git a/tests/index.test.js b/tests/index.test.js index 092c2a6..6bac43c 100644 --- a/tests/index.test.js +++ b/tests/index.test.js @@ -336,3 +336,104 @@ describe("min", () => { } ); }); +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); + }); + test("should return L", () => { + const result = (0, index_1.random)(50, 49); + expect(result).toBe("L"); + }); +}); From 916fcaa1fe34c1168ff7c2af28c353654ec0ca4d Mon Sep 17 00:00:00 2001 From: Zubair Idris Aweda Date: Sun, 19 Oct 2025 13:54:55 +0100 Subject: [PATCH 5/5] chore: fix test --- src/tests/index.test.ts | 5 ----- tests/index.test.js | 4 ---- 2 files changed, 9 deletions(-) diff --git a/src/tests/index.test.ts b/src/tests/index.test.ts index 3d4b573..f33d376 100644 --- a/src/tests/index.test.ts +++ b/src/tests/index.test.ts @@ -502,9 +502,4 @@ describe("random", () => { expect(fromRoman(result)).toBeGreaterThanOrEqual(1); expect(fromRoman(result)).toBeLessThanOrEqual(3999); }); - - test("should return L", () => { - const result = random(50, 49); - expect(result).toBe("L"); - }); }); diff --git a/tests/index.test.js b/tests/index.test.js index 6bac43c..fd21976 100644 --- a/tests/index.test.js +++ b/tests/index.test.js @@ -432,8 +432,4 @@ describe("random", () => { expect((0, index_1.fromRoman)(result)).toBeGreaterThanOrEqual(1); expect((0, index_1.fromRoman)(result)).toBeLessThanOrEqual(3999); }); - test("should return L", () => { - const result = (0, index_1.random)(50, 49); - expect(result).toBe("L"); - }); });