diff --git a/Sprint-3/1-key-implement/1-get-angle-type.js b/Sprint-3/1-key-implement/1-get-angle-type.js index 08d1f0cba..2ed0cab30 100644 --- a/Sprint-3/1-key-implement/1-get-angle-type.js +++ b/Sprint-3/1-key-implement/1-get-angle-type.js @@ -8,8 +8,11 @@ // Then, write the next test! :) Go through this process until all the cases are implemented function getAngleType(angle) { - if (angle === 90) return "Right angle"; - // read to the end, complete line 36, then pass your test here + if (angle === 90) return "Right angle"; + if (angle > 0 && angle < 90) return "Acute angle"; + if (angle > 90 && angle < 180) return "Obtuse angle"; + if (angle === 180) return "Straight angle"; + if (angle > 180 && angle < 360) return "Reflex angle"; } // we're going to use this helper function to make our assertions easier to read @@ -36,21 +39,23 @@ assertEquals(right, "Right angle"); // Case 2: Identify Acute Angles: // When the angle is less than 90 degrees, // Then the function should return "Acute angle" -const acute = getAngleType(45); +const acute = getAngleType(1); assertEquals(acute, "Acute angle"); // Case 3: Identify Obtuse Angles: // When the angle is greater than 90 degrees and less than 180 degrees, // Then the function should return "Obtuse angle" -const obtuse = getAngleType(120); -// ====> write your test here, and then add a line to pass the test in the function above +const obtuse = getAngleType(91); +assertEquals(obtuse, "Obtuse angle"); // Case 4: Identify Straight Angles: // When the angle is exactly 180 degrees, // Then the function should return "Straight angle" -// ====> write your test here, and then add a line to pass the test in the function above +const straight = getAngleType(180); +assertEquals(straight, "Straight angle"); // Case 5: Identify Reflex Angles: // When the angle is greater than 180 degrees and less than 360 degrees, // Then the function should return "Reflex angle" -// ====> write your test here, and then add a line to pass the test in the function above \ No newline at end of file +const reflex = getAngleType(181); +assertEquals(reflex, "Reflex angle"); diff --git a/Sprint-3/1-key-implement/2-is-proper-fraction.js b/Sprint-3/1-key-implement/2-is-proper-fraction.js index 91583e941..7277003a8 100644 --- a/Sprint-3/1-key-implement/2-is-proper-fraction.js +++ b/Sprint-3/1-key-implement/2-is-proper-fraction.js @@ -8,7 +8,8 @@ // write one test at a time, and make it pass, build your solution up methodically function isProperFraction(numerator, denominator) { - if (numerator < denominator) return true; + // ensure that the both inputs are evaluated to positive numbers by using Math.abs(), eg -4 returns 4, 4 returns 4, and 0 returns 0 + return Math.abs(numerator) < Math.abs(denominator); // evaluates to either true or false } // here's our helper again @@ -25,29 +26,58 @@ function assertEquals(actualOutput, targetOutput) { // Input: numerator = 2, denominator = 3 // target output: true // Explanation: The fraction 2/3 is a proper fraction, where the numerator is less than the denominator. The function should return true. -const properFraction = isProperFraction(2, 3); -assertEquals(properFraction, true); +// const properFraction = isProperFraction(2, 3); +// assertEquals(properFraction, true); // Improper Fraction check: // Input: numerator = 5, denominator = 2 // target output: false // Explanation: The fraction 5/2 is an improper fraction, where the numerator is greater than or equal to the denominator. The function should return false. -const improperFraction = isProperFraction(5, 2); -assertEquals(improperFraction, false); +// const improperFraction = isProperFraction(5, 2); +// assertEquals(improperFraction, false); // Negative Fraction check: // Input: numerator = -4, denominator = 7 // target output: true // Explanation: The fraction -4/7 is a proper fraction because the absolute value of the numerator (4) is less than the denominator (7). The function should return true. -const negativeFraction = isProperFraction(-4, 7); -// ====> complete with your assertion +// const negativeFraction = isProperFraction(-4, 7); +// assertEquals(negativeFraction, true); // Equal Numerator and Denominator check: // Input: numerator = 3, denominator = 3 // target output: false // Explanation: The fraction 3/3 is not a proper fraction because the numerator is equal to the denominator. The function should return false. -const equalFraction = isProperFraction(3, 3); -// ====> complete with your assertion +// const equalFraction = isProperFraction(3, 3); +// assertEquals(equalFraction, false); // Stretch: // What other scenarios could you test for? + +/* +Using an array to make code DRY and easier to review, using the format [numerator, denominator, expected] that will be used in the updated helper function +*/ +const testCases = [ + // original tests from earlier in file + [2, 3, true], + [5, 2, false], + [-4, 7, true], + [3, 3, false], + + // additional tests + [1, 0, false], //zero denominator input + ["2", "3", true], //string inputs + [2.5, 3.1, true], //decimal number inputs + [true, false, false], //boolean inputs + [null, 3, true], //null numerator input, (N.B. Math.abs(null) is 0, therefore this is the equivalent of evaluating to [0, 3, true]) + [2, null, false], //null denominator input, (N.B. Math.abs(null) is 0, therefore this is the equivalent of evaluating to [2, 0, false]) + [NaN, 3, false], //Nan numerator input + [2, NaN, false], //Nan denominator input + [undefined, 3, false], //undefined numerator input + [2, undefined, false], //undefined denominator input +]; + +// adjust helper function to loop through the array values, and assigns the array values to numerator, denominator, expected, eg, [2, 3, True]. +for (const [numerator, denominator, expected] of testCases) { + const result = isProperFraction(numerator, denominator); + assertEquals(result, expected); +} diff --git a/Sprint-3/1-key-implement/3-get-card-value.js b/Sprint-3/1-key-implement/3-get-card-value.js index aa1cc9f90..14a0d3a17 100644 --- a/Sprint-3/1-key-implement/3-get-card-value.js +++ b/Sprint-3/1-key-implement/3-get-card-value.js @@ -8,7 +8,22 @@ // write one test at a time, and make it pass, build your solution up methodically // just make one change at a time -- don't rush -- programmers are deep and careful thinkers function getCardValue(card) { - if (rank === "A") return 11; + // use an array to hold the values of the face cards. + const faceCards = ["J", "Q", "K"]; + + /* + use slice to extract the rank (the numeric or face card value) from the "card" value. Start at the beginning index of the string (0) and go up to but not including the last character (-1 means one from the end) + */ + const rank = card.slice(0, -1); + + if (rank === "A") return 11; //handles Ace cards + if (rank >= 2 && rank <= 10) return Number(rank); //handles cards 2-10 + + // use the includes() method to check if the value of the variable rank is one of the faceCards array values. + if (faceCards.includes(rank)) return 10; //handles face cards + + // use the built-in JS Error() object to throw an error with a message if the value of "card" is invalid (with the message as the argument) + throw new Error("invalid card rank"); //handles all invalid "card" values } // You need to write assertions for your function to check it works in different cases @@ -33,19 +48,46 @@ assertEquals(aceofSpades, 11); // When the function is called with such a card, // Then it should return the numeric value corresponding to the rank (e.g., "5" should return 5). const fiveofHearts = getCardValue("5♥"); -// ====> write your test here, and then add a line to pass the test in the function above +assertEquals(fiveofHearts, 5); // Handle Face Cards (J, Q, K): // Given a card with a rank of "10," "J," "Q," or "K", // When the function is called with such a card, // Then it should return the value 10, as these cards are worth 10 points each in blackjack. +const jackofClubs = getCardValue("J♣"); +assertEquals(jackofClubs, 10); + // Handle Ace (A): // Given a card with a rank of "A", // When the function is called with an Ace, // Then it should, by default, assume the Ace is worth 11 points, which is a common rule in blackjack. +const aceCard = getCardValue("A♦"); +assertEquals(aceCard, 11); // Handle Invalid Cards: // Given a card with an invalid rank (neither a number nor a recognized face card), // When the function is called with such a card, // Then it should throw an error indicating "Invalid card rank." +const invalidCards = [ + ["G♥", "invalid card rank"], // random rank letter + ["1♠", "invalid card rank"], // 1 is not a valid card + ["11♦", "invalid card rank"], // 11 is not a valid card + ["62♣", "invalid card rank"], //random rank number + ["", "invalid card rank"], //empty string + ["NaN♠", "invalid card rank"], //NaN as rank + ["undefined♣", "invalid card rank"], //undefined as rank + ["null♦", "invalid card rank"], // null as rank +]; + +/* + use a try block to call the getCardValue function, if it does not throw an error it checks the returned value against the expeced result. However if the function does throw an error it catches the error and checks if the error message matches "invalid card rank". In the catch block, the error.message argument in the assertEquals() function refers to Error("invalid card rank") object in the getCardValue() function at the top of the code + */ +for (const [input, expected] of invalidCards) { + try { + const result = getCardValue(input); + assertEquals(result, expected); + } catch (error) { + assertEquals(error.message, expected); + } +} diff --git a/Sprint-3/2-mandatory-rewrite/1-get-angle-type.js b/Sprint-3/2-mandatory-rewrite/1-get-angle-type.js index d61254bd7..c65e98b2d 100644 --- a/Sprint-3/2-mandatory-rewrite/1-get-angle-type.js +++ b/Sprint-3/2-mandatory-rewrite/1-get-angle-type.js @@ -1,18 +1,13 @@ function getAngleType(angle) { if (angle === 90) return "Right angle"; - // replace with your completed function from key-implement - + if (angle > 0 && angle < 90) return "Acute angle"; + if (angle > 90 && angle < 180) return "Obtuse angle"; + if (angle === 180) return "Straight angle"; + if (angle > 180 && angle < 360) return "Reflex angle"; } - - - - - - - // Don't get bogged down in this detail // Jest uses CommonJS module syntax by default as it's quite old -// We will upgrade our approach to ES6 modules in the next course module, so for now +// We will upgrade our approach to ES6 modules in the next course module, so for now // we have just written the CommonJS module.exports syntax for you -module.exports = getAngleType; \ No newline at end of file +module.exports = getAngleType; diff --git a/Sprint-3/2-mandatory-rewrite/1-get-angle-type.test.js b/Sprint-3/2-mandatory-rewrite/1-get-angle-type.test.js index b62827b7c..93e96198b 100644 --- a/Sprint-3/2-mandatory-rewrite/1-get-angle-type.test.js +++ b/Sprint-3/2-mandatory-rewrite/1-get-angle-type.test.js @@ -8,17 +8,21 @@ test("should identify right angle (90°)", () => { // make your test descriptions as clear and readable as possible // Case 2: Identify Acute Angles: -// When the angle is less than 90 degrees, -// Then the function should return "Acute angle" +test("should identify an acute angle (less than 90°)", () => { + expect(getAngleType(89)).toEqual("Acute angle"); +}); // Case 3: Identify Obtuse Angles: -// When the angle is greater than 90 degrees and less than 180 degrees, -// Then the function should return "Obtuse angle" +test("should identify an obtuse angle (greater than 90°)", () => { + expect(getAngleType(91)).toEqual("Obtuse angle"); +}); // Case 4: Identify Straight Angles: -// When the angle is exactly 180 degrees, -// Then the function should return "Straight angle" +test("should identify an straight angle (exactly 180°)", () => { + expect(getAngleType(180)).toEqual("Straight angle"); +}); // Case 5: Identify Reflex Angles: -// When the angle is greater than 180 degrees and less than 360 degrees, -// Then the function should return "Reflex angle" +test("should identify a reflex angle (greater than 180° but less than 360°)", () => { + expect(getAngleType(181)).toEqual("Reflex angle"); +}); diff --git a/Sprint-3/2-mandatory-rewrite/2-is-proper-fraction.js b/Sprint-3/2-mandatory-rewrite/2-is-proper-fraction.js index 9836fe398..6cd433d3a 100644 --- a/Sprint-3/2-mandatory-rewrite/2-is-proper-fraction.js +++ b/Sprint-3/2-mandatory-rewrite/2-is-proper-fraction.js @@ -1,6 +1,7 @@ function isProperFraction(numerator, denominator) { - if (numerator < denominator) return true; - // add your completed function from key-implement here + // ensure that the both inputs are evaluated to positive numbers by using Math.abs(), eg -4 returns 4, 4 returns 4, and 0 returns 0 + if (Math.abs(numerator) < Math.abs(denominator)) return true; + return false; //if the inputs don't satisfy the expression it returns false } -module.exports = isProperFraction; \ No newline at end of file +module.exports = isProperFraction; diff --git a/Sprint-3/2-mandatory-rewrite/2-is-proper-fraction.test.js b/Sprint-3/2-mandatory-rewrite/2-is-proper-fraction.test.js index ff1cc8173..a072a5d3e 100644 --- a/Sprint-3/2-mandatory-rewrite/2-is-proper-fraction.test.js +++ b/Sprint-3/2-mandatory-rewrite/2-is-proper-fraction.test.js @@ -5,7 +5,16 @@ test("should return true for a proper fraction", () => { }); // Case 2: Identify Improper Fractions: +test("should return true for a improper fraction", () => { + expect(isProperFraction(5, 2)).toEqual(false); +}); // Case 3: Identify Negative Fractions: +test("should return true for a negative fraction", () => { + expect(isProperFraction(-4, -7)).toEqual(true); +}); // Case 4: Identify Equal Numerator and Denominator: +test("should return true for an equal numerator and denominator", () => { + expect(isProperFraction(3, 3)).toEqual(false); +}); diff --git a/Sprint-3/2-mandatory-rewrite/3-get-card-value.js b/Sprint-3/2-mandatory-rewrite/3-get-card-value.js index 0d95d3736..c1d7d8f6d 100644 --- a/Sprint-3/2-mandatory-rewrite/3-get-card-value.js +++ b/Sprint-3/2-mandatory-rewrite/3-get-card-value.js @@ -1,5 +1,20 @@ function getCardValue(card) { - // replace with your code from key-implement - return 11; + // use an array to hold the values of the face cards. + const faceCards = ["10", "J", "Q", "K"]; + + /* + use slice to extract the rank (the numeric or face card value) from the "card" value. Start at the beginning index of the string (0) and go up to but not including the last character (-1 means one from the end) + */ + const rank = card.slice(0, -1); + + if (rank === "A") return 11; //handles Ace cards + if (Number(rank) >= 2 && Number(rank) < 10) return Number(rank); //handles cards 2-9 + + // use the includes() method to check if the value of the variable rank is one of the faceCards array values. + if (faceCards.includes(rank)) return 10; //handles face cards + + // use the built-in JS Error() object to throw an error with a message if the value of "card" is invalid (with the message as the argument) + throw new Error("Invalid card rank"); //handles all invalid "card" values } -module.exports = getCardValue; \ No newline at end of file + +module.exports = getCardValue; diff --git a/Sprint-3/2-mandatory-rewrite/3-get-card-value.test.js b/Sprint-3/2-mandatory-rewrite/3-get-card-value.test.js index 03a8e2f34..ce8d9d1e2 100644 --- a/Sprint-3/2-mandatory-rewrite/3-get-card-value.test.js +++ b/Sprint-3/2-mandatory-rewrite/3-get-card-value.test.js @@ -1,11 +1,56 @@ const getCardValue = require("./3-get-card-value"); test("should return 11 for Ace of Spades", () => { - const aceofSpades = getCardValue("A♠"); - expect(aceofSpades).toEqual(11); - }); + const aceofSpades = getCardValue("A♠"); + expect(aceofSpades).toEqual(11); +}); // Case 2: Handle Number Cards (2-10): +test("should return correlating number for cards with ranks between 2-10", () => { + const numberCard = getCardValue("5♥"); + expect(numberCard).toEqual(5); +}); // Case 3: Handle Face Cards (J, Q, K): +test("should return 10 for face cards of J, Q, K", () => { + const faceCard = getCardValue("J♣"); + expect(faceCard).toEqual(10); +}); // Case 4: Handle Ace (A): -// Case 5: Handle Invalid Cards: +test("should return 11 for Ace of Spades", () => { + const aceCard = getCardValue("A♦"); + expect(aceCard).toEqual(11); +}); +// Case 5: Handle Invalid Cards - using an array for the inputs : +// test("should throw an error for invalid card ranks", () => { +// const invalidCardInput = [ +// "G♥", // random rank letter +// "1♠", // 1 is not a valid card +// "11♦", // 11 is not a valid card +// "62♣", //random rank number +// "", //empty string +// "NaN♠", //NaN as rank +// "undefined♣", //undefined as rank +// "null♦", // null as rank +// ]; + + // I have learned that we can use Jest's .each() for clearer test output, better reporting, parallel execution (improves test performance) and less boilerplate code. in the description of the test we can use placeholders such as %s (string value) and %d (number , or digit, value) as placeholders for the actual test cases inputs, meaning that when the error message appears we can immediately know which case it refers to. E.g if the current input is "G♥", the test name will be: should throw an error for invalid card rank: G♥" + test.each([ + "G♥", // random rank letter + "1♠", // 1 is not a valid card + "11♦", // 11 is not a valid card + "62♣", // random rank number + "", // empty string + "NaN♠", // NaN as rank + "undefined♣", // undefined as rank + "null♦", // null as rank + ])("should throw an error for invalid card rank: %s", (input) => { + expect(() => getCardValue(input)).toThrow("Invalid card rank"); + }); + + for (const input of invalidCardInput) { + expect(() => getCardValue(input)).toThrow("Invalid card rank"); + /* +Jest doesn;t test the value itself, it tests whether the function is throwing an error. If getCardValue(input) is called directly (i.e. using: expect(getCardValue(input)).toThrow("Invalid card rank") instead), the error happens immediately and Jest never gets a chance to check it, therefore the test fails. Wrapping getCardValue(input) in an anonymous function ensures that it delays the function execution allowing Jest to observe the error instead of triggering it too soon. +*/ + } +}); diff --git a/Sprint-3/3-mandatory-practice/implement/count.js b/Sprint-3/3-mandatory-practice/implement/count.js index fce249650..5b2e44048 100644 --- a/Sprint-3/3-mandatory-practice/implement/count.js +++ b/Sprint-3/3-mandatory-practice/implement/count.js @@ -1,5 +1,12 @@ function countChar(stringOfCharacters, findCharacter) { - return 5 + let count = 0; + for (const char of stringOfCharacters) { + //loops through each character in the string input + if (char === findCharacter) + //checks if the character matches the findCharacter input + count++; //increases the count when a match is found in the input + } + return count; //return the actual count of the characters that match } -module.exports = countChar; \ No newline at end of file +module.exports = countChar; diff --git a/Sprint-3/3-mandatory-practice/implement/count.test.js b/Sprint-3/3-mandatory-practice/implement/count.test.js index 42baf4b4b..d23f671a7 100644 --- a/Sprint-3/3-mandatory-practice/implement/count.test.js +++ b/Sprint-3/3-mandatory-practice/implement/count.test.js @@ -22,3 +22,9 @@ test("should count multiple occurrences of a character", () => { // And a character char that does not exist within the case-sensitive str, // When the function is called with these inputs, // Then it should return 0, indicating that no occurrences of the char were found in the case-sensitive str. +test("should count no occurrences of a character", () => { + const str = "aaaaa"; + const char = "z"; + const count = countChar(str, char); + expect(count).toEqual(0); +}); diff --git a/Sprint-3/3-mandatory-practice/implement/get-ordinal-number.js b/Sprint-3/3-mandatory-practice/implement/get-ordinal-number.js index 24f528b0d..4e5c687da 100644 --- a/Sprint-3/3-mandatory-practice/implement/get-ordinal-number.js +++ b/Sprint-3/3-mandatory-practice/implement/get-ordinal-number.js @@ -1,5 +1,28 @@ -function getOrdinalNumber(num) { - return "1st"; +function getOrdinalNumber(n) { + // handle invalid inputs such as non-numeric inputs + if (typeof n !== "number" || !Number.isInteger(n) || n < 0) + throw new Error("Invalid ordinal number"); + /* + There is a mathematical approach to determine the correct order suffix for ordinal numbers using modulo opertions. Most numbers follow a rule based on the lst digit, ie. if the last digit is 1, 2 or 3 use "st", "nd", "rd" respectively and for everything else use "th". The exception to the rule is for numbers ending in 11, 12, or 13, they always end in "th". For the last digit of a number use modulo 10 (e.g. 21 % 10 = 1, 102 % 10 = 2, etc), for the last two digits of a number use modulo 100 (e.g. 111 % 100 = 11, 11) + */ + + //extract common values into a variable + const lastDigit = n & 10; + const lastTwoDigits = n % 100; + + if (lastTwoDigits === 11 || lastTwoDigits === 12 || lastTwoDigits === 13) { + return n + "th"; + } + if (lastDigit === 1) { + return n + "st"; + } + if (lastDigit === 2) { + return n + "nd"; + } + if (lastDigit === 3) { + return n + "rd"; + } + return n + "th"; //everything else in numbers } -module.exports = getOrdinalNumber; \ No newline at end of file +module.exports = getOrdinalNumber; diff --git a/Sprint-3/3-mandatory-practice/implement/get-ordinal-number.test.js b/Sprint-3/3-mandatory-practice/implement/get-ordinal-number.test.js index 6d55dfbb4..f7a1dd1ad 100644 --- a/Sprint-3/3-mandatory-practice/implement/get-ordinal-number.test.js +++ b/Sprint-3/3-mandatory-practice/implement/get-ordinal-number.test.js @@ -9,5 +9,44 @@ const getOrdinalNumber = require("./get-ordinal-number"); // Then the function should return "1st" test("should return '1st' for 1", () => { - expect(getOrdinalNumber(1)).toEqual("1st"); - }); + expect(getOrdinalNumber(1)).toEqual("1st"); +}); + +// additional cases for testing: + +// normal tests +test("should return 'st', 'nd', 'rd' or 'th'", () => { + const normalInput = [ + [1, "1st"], + [2, "2nd"], + [3, "3rd"], + [4, "4th"], + [11, "11th"], + [12, "12th"], + [13, "13th"], + [21, "21st"], + [22, "22nd"], + [23, "23rd"], + [100, "100th"], + [101, "101st"], + [102, "102nd"], + [103, "103rd"], + [111, "111th"], + [112, "112th"], + [113, "113th"], + [1000, "1000th"], + ]; + + for (const [input, expected] of normalInput) { + expect(getOrdinalNumber(input)).toEqual(expected); + } +}); + +// invalid tests +test("should throw an error for invalid ordinal numbers", () => { + const invalidInputs = ["hello", "123abc", "", null, undefined, [], {}]; + + for (const input of invalidInputs) { + expect(() => getOrdinalNumber(input)).toThrow("Invalid ordinal number"); + } +}); diff --git a/Sprint-3/3-mandatory-practice/implement/repeat.js b/Sprint-3/3-mandatory-practice/implement/repeat.js index 621f9bd35..3075fb2dc 100644 --- a/Sprint-3/3-mandatory-practice/implement/repeat.js +++ b/Sprint-3/3-mandatory-practice/implement/repeat.js @@ -1,5 +1,11 @@ -function repeat() { - return "hellohellohello"; +function repeat(str, count) { + if (typeof count !== "number" || !Number.isInteger(count) || isNaN(count)) { + throw new Error("Count is an invalid input"); + } + if (count < 0) { + throw new Error("Count must be a non-negative integer"); + } + return str.repeat(count); } -module.exports = repeat; \ No newline at end of file +module.exports = repeat; diff --git a/Sprint-3/3-mandatory-practice/implement/repeat.test.js b/Sprint-3/3-mandatory-practice/implement/repeat.test.js index 8a4ab42ef..d08ce0b87 100644 --- a/Sprint-3/3-mandatory-practice/implement/repeat.test.js +++ b/Sprint-3/3-mandatory-practice/implement/repeat.test.js @@ -10,23 +10,54 @@ const repeat = require("./repeat"); // Then it should repeat the str count times and return a new string containing the repeated str values. test("should repeat the string count times", () => { - const str = "hello"; - const count = 3; - const repeatedStr = repeat(str, count); - expect(repeatedStr).toEqual("hellohellohello"); - }); + const str = "hello"; + const count = 3; + const repeatedStr = repeat(str, count); + expect(repeatedStr).toEqual("hellohellohello"); +}); // case: handle Count of 1: // Given a target string str and a count equal to 1, // When the repeat function is called with these inputs, // Then it should return the original str without repetition, ensuring that a count of 1 results in no repetition. +test("should return the string without repetition", () => { + const str = "hello"; + const count = 1; + const repeatedStr = repeat(str, count); + expect(repeatedStr).toEqual("hello"); +}); + // case: Handle Count of 0: // Given a target string str and a count equal to 0, // When the repeat function is called with these inputs, // Then it should return an empty string, ensuring that a count of 0 results in an empty output. +test("should return an empty string ''", () => { + const str = "hello"; + const count = 0; + const repeatedStr = repeat(str, count); + expect(repeatedStr).toEqual(""); +}); // case: Negative Count: // Given a target string str and a negative integer count, // When the repeat function is called with these inputs, // Then it should throw an error or return an appropriate error message, as negative counts are not valid. + +test("should throw an error for negative count", () => { + const str = "hello"; + const count = -1; + expect(() => repeat(str, count)).toThrow( + "Count must be a non-negative integer" + ); +}); + +// case: Invalid Count Type: +// Given a target string str and an invalid count type (e.g., a string or an object), +// When the repeat function is called with these inputs, +// Then it should throw an error or return an appropriate error message, as the count must be a non-negative integer. +test("should return the message 'Count is an invalid input'", () => { + const str = "hello"; + const count = "three"; + expect(() => repeat(str, count)).toThrow("Count is an invalid input"); +}); diff --git a/Sprint-3/4-stretch-investigate/card-validator.js b/Sprint-3/4-stretch-investigate/card-validator.js new file mode 100644 index 000000000..7421fd8f2 --- /dev/null +++ b/Sprint-3/4-stretch-investigate/card-validator.js @@ -0,0 +1,90 @@ +/* + Helper function to check if the card number is a string using the typeof operator. + Because credit card numbers can start with zero, using a "number" type would mean dropping any leading zeros which would result in loss of information. +*/ +function isString(cardNumber) { + return typeof cardNumber === "string"; +} + +/* + Helper function to check that the card number is exactly 16 digits using the length property which checks the length of a string. +*/ +function isSixteenDigits(cardNumber) { + return cardNumber.length === 16; +} + +/* +Helper function to check all characters in the card number are digits. +.split("") turns the string into an array of characters. +.every() tests whether all characters in the card number pass the provided function parameters (char >= "0" && char <= "9"). In JavaScript, when you compare characters using >= or <=, it compares their Unicode (ASCII) values.The character "0" has the value 47 and "9" has the value 57. Therefore any letters or special characters outside this range will return false, +and ensures the card number does not contain any letters or special characters. +*/ +function isAllDigits(cardNumber) { + return cardNumber.split("").every((char) => char >= "0" && char <= "9"); +} + +/* +Helper function to check that there must be at least two unique digits in the card number. +Set is an object that lets you store unique values of any type. So new Set(cardNumber) will create a set of unique characters from the card number string. .size checks the number of unique characters in the set. Here it will return true if there are 2 or more unique digits (e.g. 1111114111111111) +*/ +function hasAtLeastTwoDifferentDigits(cardNumber) { + //pulls only the unique characters from the string + const twoDifferentDigits = new Set(cardNumber); + //checks if we have 2 or more unique ones + return twoDifferentDigits.size >= 2; +} + +/* +Helper function to check if the last single digit in the card number is even. .length finds out how many characters are in a string and -1 gets the last character in the string (it is the equivalent of the 16th number in the credit card number. In javascript to get the end character in a string or an array we count from the right side of the string which starts at -1). +Number() converts it to a number, and % 2 checks if it's even by assessing if when divided by 2 it has a remainder of 0. +*/ +function isLastDigitEven(cardNumber) { + const lastDigit = Number(cardNumber[cardNumber.length - 1]); + return lastDigit % 2 === 0; +} + +/* +Helper function to check if the sum of all digits is greater than 16. +The for...of loop iterates over each character (num) in the string input of cardNumber. +Number(num) converts each character to a number before adding to sum. +*/ +function isSumGreaterThanSixteen(cardNumber) { + let sum = 0; + for (const num of cardNumber) { + sum += Number(num); + } + return sum > 16; +} + +// Main validation function to validate a credit card number. The argument for the function is the credit card number + +function isValidCardNumber(cardNumber) { + //if the card number input is not a "string" type. + if (!isString(cardNumber)) { + throw new Error("Card number must be a string"); + } + //if the card number is not exactly 16 digits + if (!isSixteenDigits(cardNumber)) { + throw new Error("Card number must be exactly 16 digits"); + } + //if all characters in the card number are not digits + if (!isAllDigits(cardNumber)) { + throw new Error("Card number must contain only digits"); + } + //if the card number only has one recurring type of digit + if (!hasAtLeastTwoDifferentDigits(cardNumber)) { + throw new Error("Card number must contain at least two unique digits"); + } + //if the last digit is not even + if (!isLastDigitEven(cardNumber)) { + throw new Error("The last digit must be an even number"); + } + //if the sum of all digits is less than 16 + if (!isSumGreaterThanSixteen(cardNumber)) { + throw new Error("The sum of all digits must be greater than 16"); + } + // If all checks pass, return true + return true; +} + +module.exports = isValidCardNumber; diff --git a/Sprint-3/4-stretch-investigate/card-validator.test.js b/Sprint-3/4-stretch-investigate/card-validator.test.js new file mode 100644 index 000000000..9d206048c --- /dev/null +++ b/Sprint-3/4-stretch-investigate/card-validator.test.js @@ -0,0 +1,115 @@ +const isValidCardNumber = require("./card-validator"); + +/* +Testing the is ValidCardNumber function with various test cases. +Each test case checks a specific condition of the card number validation. +*/ + +// test case 1: card number is a string +test("card number is a string", () => { + expect(isValidCardNumber("1234567890123456")).toBe(true); +}); + +// test case 2: card number is not a string +test("card number is not a string", () => { + expect(isValidCardNumber(1234567890123456)).toBe( + "Card number must be a string" + ); +}); + +// test case 3: card number contains only 16 digits +test("card number with 16 digits returns true", () => { + expect(isValidCardNumber("1234567890123456")).toBe(true); +}); + +// test case 4: card number contains less than 16 digits +test("card number with less than 16 digits returns error message", () => { + expect(isValidCardNumber("123456789012345")).toBe( + "Card number must be exactly 16 digits" + ); +}); + +// test case 5: card number contains more than 16 digits +test("card number with more than 16 digits returns error message", () => { + expect(isValidCardNumber("12345678901234567")).toBe( + "Card number must be exactly 16 digits" + ); +}); + +// test case 6: card number contains at least 2 unique digits +test("card number with at least 2 unique digits returns true", () => { + expect(isValidCardNumber("1112111311111114")).toBe(true); +}); + +// test case 7: card number contains only one unique digit +test("Card number must contain at least two unique digits", () => { + expect(isValidCardNumber("1111111111111111")).toBe( + "Card number must contain at least two unique digits" + ); +}); + +// test case 8: card number last single digit is even +test("card number with exactly 16 digits and last single digit even returns true", () => { + expect(isValidCardNumber("1234567890123456")).toBe(true); +}); + +// test case 9: card number last single digit is odd +test("card number with exactly 16 digits and last digit odd returns error message", () => { + expect(isValidCardNumber("1234567890123455")).toBe( + "The last digit must be an even number" + ); +}); + +// test case 10: the sum of all card number digits is greater than 16 +test("card number with sum of digits greater than 16 returns true", () => { + expect(isValidCardNumber("1234567890123456")).toBe(true); +}); + +// test case 11: the sum of all card number digits is less than or equal to 16 +test("card number with sum of digits less than or equal to 16 returns error message", () => { + expect(isValidCardNumber("1111111111111110")).toBe( + "The sum of all digits must be greater than 16" + ); +}); + +// test case 12: card number contains non-digit characters +test("card number with non-digit characters returns error message", () => { + expect(isValidCardNumber("1234a67890123456")).toBe( + "Card number must contain only digits" + ); +}); + +// test case 13: card number contains special characters +test("card number with special characters returns error message", () => { + expect(isValidCardNumber("1234-5678-9012-3456")).toBe( + "Card number must be exactly 16 digits" + ); +}); + +// test case 14: card number contains spaces +test("card number with spaces returns error message", () => { + expect(isValidCardNumber("1234 5678 9012 3456")).toBe( + "Card number must be exactly 16 digits" + ); +}); + +// test case 15: card number is empty +test("empty card number returns error message", () => { + expect(isValidCardNumber("")).toBe("Card number must be exactly 16 digits"); +}); + +// test case 16: card number is null +test("null card number returns error message", () => { + expect(isValidCardNumber(null)).toBe("Card number must be a string"); +}); + +// test case 17: card number is undefined +test("undefined card number returns error message", () => { + expect(isValidCardNumber(undefined)).toBe("Card number must be a string"); +}); + +// test case 18: card number is a string with leading zeros +test("card number with leading zeros returns true", () => { + expect(isValidCardNumber("0000123456789012")).toBe(true); +}); +// test case 19: card number is a string with trailing spaces diff --git a/Sprint-3/4-stretch-investigate/find.js b/Sprint-3/4-stretch-investigate/find.js index c7e79a2f2..1f83a8496 100644 --- a/Sprint-3/4-stretch-investigate/find.js +++ b/Sprint-3/4-stretch-investigate/find.js @@ -20,6 +20,21 @@ console.log(find("code your future", "z")); // Pay particular attention to the following: // a) How the index variable updates during the call to find +/* +The index variable is set at 0 at the start of the fucntion. Each time the while loop runs it increases the index by 1. In other words it moves through each index position in the string, checking each character as it loops through the string. +*/ + // b) What is the if statement used to check +/* +The if statement checks that the character a the current index in the string (str[index]) is equal to the target character (char). If it is equal then the function returns the index of that character. +*/ + // c) Why is index++ being used? +/* +Index++ is exactly the same as saying each time the loop runs increase index by 1, or "index = index + 1". It is being used to move through each character in the string one by one. The index represents the current position of the character in the string being checked. +*/ + // d) What is the condition index < str.length used for? +/* +This condition is used to make sure that the index doesn;t go beyond the length of the string (we don't want it to go past the end of the string and cause an error). Essentially we are limiting checking the index of each character to the length of the string. +*/ diff --git a/Sprint-3/4-stretch-investigate/password-validator.js b/Sprint-3/4-stretch-investigate/password-validator.js index b55d527db..b0c6d8a3e 100644 --- a/Sprint-3/4-stretch-investigate/password-validator.js +++ b/Sprint-3/4-stretch-investigate/password-validator.js @@ -1,6 +1,21 @@ -function passwordValidator(password) { - return password.length < 5 ? false : true -} +// This function validates a password based on several criteria: + +function passwordValidator(password, previousPasswords = []) { + //"return password.length < 5 ? false : true;" - Only this first return statement will ever run. As soon as a return is executed the function exits and the rest of the code is ignored + if (typeof password !== "string") return false; //checks if the password is a not a string type (i.e. a value such as a number ,object, array , undefined, NaN, null, etc.), returns false if it is not a string. + if (password.length > 100) return false; //checks that the password is not longer than 100 characters + if (password.length < 5) return false; //checks that the password is at least a minimum of 5 characters long + /* + The following checks if the password contains at specific characters using the regex and the .test() method. The .test method is used to check if a string matches a pattern. The "!" negates the result, so it returns false if no uppercase letter is found. + */ + if (!/[A-Z]/.test(password)) return false; // checks if the password contains at least one uppercase letter using the regex /[A-Z]/, returns false if no uppercase letter is found. + if (!/[a-z]/.test(password)) return false; // checks if the password contains at least one lowercase letter using the regex /[a-z]/, returns false if no loercase letter is found. + if (!/[0-9]/.test(password)) return false; // checks if the password contains at least one number using the regex /[0-9]/, returns false if no number is found. + if (!/[!#$%.&*]/.test(password)) return false; // checks if the password contains at least one non-alphanumeric character using the regex [!#$%.&*]/, returns false if no character is found. + + if (previousPasswords.includes(password)) return false; //checks that the password is not in the previous passwords array + return true; +} -module.exports = passwordValidator; \ No newline at end of file +module.exports = passwordValidator; diff --git a/Sprint-3/4-stretch-investigate/password-validator.test.js b/Sprint-3/4-stretch-investigate/password-validator.test.js index 8fa3089d6..c2bd0591d 100644 --- a/Sprint-3/4-stretch-investigate/password-validator.test.js +++ b/Sprint-3/4-stretch-investigate/password-validator.test.js @@ -14,13 +14,38 @@ To be valid, a password must: You must breakdown this problem in order to solve it. Find one test case first and get that working */ + const isValidPassword = require("./password-validator"); -test("password has at least 5 characters", () => { - // Arrange - const password = "12345"; - // Act - const result = isValidPassword(password); - // Assert - expect(result).toEqual(true); -} -); \ No newline at end of file +// I have decided to use an array for the test cases to keep code clear and ensuring DRY code. +const testCases = [ + // uses the format of "description, password, previousPasswords, expectedResult" in the array + ["password has less than 5 characters", "123!", [], false], + ["password does not have an English uppercase letter", "1234*abc", [], false], + ["password does not have a number", "Abcdef&", [], false], + ["password does not have a non-alphanumeric character", "Abc123", [], false], + [ + "password is in previous passwords array", + "Abcde3!", + ["Abcde1!", "Abcde2!"], + true, + ], + ["password is undefined", undefined, [], false], + ["password is null", null, [], false], + ["password is a number", 123456, [], false], + ["password is an array", ["Abc123!"], [], false], + ["password is an object", { pass: "Abc123!" }, [], false], + ["password is an empty string", "", [], false], + [ + "valid password that passes all checks", + "1234*Abc", + ["Password1!", "Zyx987*"], + true, + ], +]; +// for each test of tes cases it takes the inputs of the array and then runs the test. +testCases.forEach(([description, password, previousPasswords, expected]) => { + test(description, () => { + // Act and Assert + expect(isValidPassword(password, previousPasswords)).toEqual(expected); + }); +});