diff --git a/Sprint-3/2-practice-tdd/count.js b/Sprint-3/2-practice-tdd/count.js index 95b6ebb7d4..888f7bb5d5 100644 --- a/Sprint-3/2-practice-tdd/count.js +++ b/Sprint-3/2-practice-tdd/count.js @@ -1,5 +1,50 @@ +/** + * countChar(stringOfCharacters, findCharacter) + * + * Count the number of times a single character appears in a string. + * + * Behaviour / contract: + * - The function is case-sensitive: 'a' ≠ 'A'. + * - `stringOfCharacters` may be `null` or `undefined`; in that case the function returns 0. + * - `findCharacter` is treated as a single character (a single Unicode code point). + * If you pass a longer string it will never match. + * + * Parameters: + * @param {string|null|undefined} stringOfCharacters - The string to search through. + * @param {string} findCharacter - The single character to count (should be length 1). + * + * Returns: + * @returns {number} The number of occurrences of `findCharacter` in `stringOfCharacters`. + * + * Throws: + * - Does not throw for `null`/`undefined` source; returns 0. + * - If you want stricter validation for types or for multi-character `findCharacter`, + * validate and throw in the implementation and update tests accordingly. + * + * Examples: + * // basic + * countChar('aaaaa', 'a'); // 5 + * countChar('Hello', 'l'); // 2 + * + * // empty/absent inputs + * countChar('', 'a'); // 0 + * countChar(null, 'a'); // 0 + * + * Notes: + * - Uses strict equality (===) so it matches exact code points. + * - For emoji or other characters outside the BMP this implementation (for..of) + * iterates code points, which handles many emoji correctly. + */ + function countChar(stringOfCharacters, findCharacter) { - return 5 + if (stringOfCharacters == null) return 0; + let count = 0; + for (const char of stringOfCharacters) { + if (char === findCharacter) { + count++; + } + } + return count; } module.exports = countChar; diff --git a/Sprint-3/2-practice-tdd/count.test.js b/Sprint-3/2-practice-tdd/count.test.js index 179ea0ddf7..d5da9a01ce 100644 --- a/Sprint-3/2-practice-tdd/count.test.js +++ b/Sprint-3/2-practice-tdd/count.test.js @@ -22,3 +22,22 @@ test("should count multiple occurrences of a character", () => { // And a character `char` that does not exist within `str`. // When the function is called with these inputs, // Then it should return 0, indicating that no occurrences of `char` were found. +test("return 0 when the character is not found", () => { + const str = "hello world"; + const char = "x"; + const count = countChar(str, char); + expect(count).toEqual(0); +}); + +test("handles empty source string", () => { + const str = ""; + const char = "a"; + const count = countChar(str, char); + expect(count).toEqual(0); +}); + +test("handles null or undefined source string", () => { + const char = "a"; + expect(countChar(null, char)).toEqual(0); + expect(countChar(undefined, char)).toEqual(0); +}); diff --git a/Sprint-3/2-practice-tdd/get-ordinal-number.js b/Sprint-3/2-practice-tdd/get-ordinal-number.js index f95d71db13..c470ad54f8 100644 --- a/Sprint-3/2-practice-tdd/get-ordinal-number.js +++ b/Sprint-3/2-practice-tdd/get-ordinal-number.js @@ -1,5 +1,25 @@ function getOrdinalNumber(num) { - return "1st"; + // Validate input: must be a number (not a string, null or undefined) and not NaN + if (typeof num !== "number" || Number.isNaN(num)) { + throw new TypeError("Input must be a number"); + } + + const abs = Math.abs(num); + const lastTwoDigits = abs % 100; + if (lastTwoDigits >= 11 && lastTwoDigits <= 13) { + return `${num}th`; + } + const lastDigit = lastTwoDigits % 10; + if (lastDigit === 1) { + return `${num}st`; + } + if (lastDigit === 2) { + return `${num}nd`; + } + if (lastDigit === 3) { + return `${num}rd`; + } + return `${num}th`; } module.exports = getOrdinalNumber; diff --git a/Sprint-3/2-practice-tdd/get-ordinal-number.test.js b/Sprint-3/2-practice-tdd/get-ordinal-number.test.js index adfa58560f..4e9284a242 100644 --- a/Sprint-3/2-practice-tdd/get-ordinal-number.test.js +++ b/Sprint-3/2-practice-tdd/get-ordinal-number.test.js @@ -13,8 +13,61 @@ const getOrdinalNumber = require("./get-ordinal-number"); // Case 1: Numbers ending with 1 (but not 11) // When the number ends with 1, except those ending with 11, // Then the function should return a string by appending "st" to the number. -test("should append 'st' for numbers ending with 1, except those ending with 11", () => { - expect(getOrdinalNumber(1)).toEqual("1st"); - expect(getOrdinalNumber(21)).toEqual("21st"); - expect(getOrdinalNumber(131)).toEqual("131st"); +test("should append 'st' for numbers ending with 1, excluding 11", () => { + expect(getOrdinalNumber(1)).toBe("1st"); + expect(getOrdinalNumber(21)).toBe("21st"); + expect(getOrdinalNumber(101)).toBe("101st"); + expect(getOrdinalNumber(131)).toBe("131st"); +}); + +test("should append 'nd' for numbers ending with 2, excluding 12", () => { + expect(getOrdinalNumber(2)).toBe("2nd"); + expect(getOrdinalNumber(22)).toBe("22nd"); + expect(getOrdinalNumber(132)).toBe("132nd"); +}); + +test("should append 'rd' for numbers ending with 3, excluding 13", () => { + expect(getOrdinalNumber(3)).toBe("3rd"); + expect(getOrdinalNumber(23)).toBe("23rd"); + expect(getOrdinalNumber(133)).toBe("133rd"); +}); + +test("should append 'th' for numbers ending with 11, 12, or 13", () => { + expect(getOrdinalNumber(11)).toBe("11th"); + expect(getOrdinalNumber(12)).toBe("12th"); + expect(getOrdinalNumber(13)).toBe("13th"); + expect(getOrdinalNumber(111)).toBe("111th"); + expect(getOrdinalNumber(112)).toBe("112th"); + expect(getOrdinalNumber(113)).toBe("113th"); +}); + +test("should append 'th' for numbers ending with 4-9 or 0", () => { + expect(getOrdinalNumber(4)).toBe("4th"); + expect(getOrdinalNumber(5)).toBe("5th"); + expect(getOrdinalNumber(6)).toBe("6th"); + expect(getOrdinalNumber(7)).toBe("7th"); + expect(getOrdinalNumber(8)).toBe("8th"); + expect(getOrdinalNumber(9)).toBe("9th"); + expect(getOrdinalNumber(0)).toBe("0th"); +}); + +test("non-integer and negative numbers should be handled correctly", () => { + expect(getOrdinalNumber(1.5)).toBe("1.5th"); + expect(getOrdinalNumber(-2.3)).toBe("-2.3th"); + expect(getOrdinalNumber(-11)).toBe("-11th"); +}); + +test("invalid input should throw an error", () => { + expect(() => getOrdinalNumber("string")).toThrow("Input must be a number"); + expect(() => getOrdinalNumber(null)).toThrow("Input must be a number"); + expect(() => getOrdinalNumber(undefined)).toThrow("Input must be a number"); +}); + +test("NaN input should throw an error", () => { + expect(() => getOrdinalNumber(NaN)).toThrow("Input must be a number"); +}); + +test("Infinity input should be handled correctly", () => { + expect(getOrdinalNumber(Infinity)).toBe("Infinityth"); + expect(getOrdinalNumber(-Infinity)).toBe("-Infinityth"); }); diff --git a/Sprint-3/2-practice-tdd/repeat-str.js b/Sprint-3/2-practice-tdd/repeat-str.js index 2af0a2cea7..1564f83d72 100644 --- a/Sprint-3/2-practice-tdd/repeat-str.js +++ b/Sprint-3/2-practice-tdd/repeat-str.js @@ -1,7 +1,16 @@ -function repeatStr() { - // Your implementation of this function must *not* call String.prototype.repeat (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat). - // The goal is to re-implement that function, not to use it. - return "hellohellohello"; +function repeatStr(str, count) { + if (typeof str !== "string") { + throw new TypeError("First argument must be a string"); + } + if (!Number.isInteger(count) || count < 0) { + throw new TypeError("Count must be a non-negative integer"); + } + + let result = ""; + for (let i = 0; i < count; i++) { + result += str; + } + return result; } module.exports = repeatStr; diff --git a/Sprint-3/2-practice-tdd/repeat-str.test.js b/Sprint-3/2-practice-tdd/repeat-str.test.js index a3fc1196c4..12f3cf033e 100644 --- a/Sprint-3/2-practice-tdd/repeat-str.test.js +++ b/Sprint-3/2-practice-tdd/repeat-str.test.js @@ -20,13 +20,99 @@ test("should repeat the string count times", () => { // Given a target string `str` and a `count` equal to 1, // When the repeatStr function is called with these inputs, // Then it should return the original `str` without repetition. - +test("should return the original string when count is 1", () => { + const str = "world"; + const count = 1; + const repeatedStr = repeatStr(str, count); + expect(repeatedStr).toEqual("world"); +}); // Case: Handle count of 0: // Given a target string `str` and a `count` equal to 0, // When the repeatStr function is called with these inputs, // Then it should return an empty string. +test("should return an empty string when count is 0", () => { + const str = "test"; + const count = 0; + const repeatedStr = repeatStr(str, count); + expect(repeatedStr).toEqual(""); +}); // Case: Handle negative count: // Given a target string `str` and a negative integer `count`, // When the repeatStr function is called with these inputs, // Then it should throw an error, as negative counts are not valid. +test("should throw an error for negative count", () => { + const str = "error"; + const count = -2; + expect(() => repeatStr(str, count)).toThrow( + "Count must be a non-negative integer" + ); +}); + +test("should throw an error for non-integer count", () => { + const str = "error"; + const count = 2.5; + expect(() => repeatStr(str, count)).toThrow( + "Count must be a non-negative integer" + ); +}); + +test("should throw an error for non-string input", () => { + const str = 123; + const count = 3; + expect(() => repeatStr(str, count)).toThrow( + "First argument must be a string" + ); +}); + +test("should throw an error for null or undefined input", () => { + const count = 3; + expect(() => repeatStr(null, count)).toThrow( + "First argument must be a string" + ); + expect(() => repeatStr(undefined, count)).toThrow( + "First argument must be a string" + ); +}); + +test("handle empty string input", () => { + const str = ""; + const count = 5; + const repeatedStr = repeatStr(str, count); + expect(repeatedStr).toEqual(""); +}); + +test("handle large count input", () => { + const str = "a"; + const count = 1000; + const repeatedStr = repeatStr(str, count); + expect(repeatedStr).toEqual("a".repeat(count)); +}); + +test("handle special characters in string", () => { + const str = "!@#"; + const count = 4; + const repeatedStr = repeatStr(str, count); + expect(repeatedStr).toEqual("!@#!@#!@#!@#"); +}); + +test("handle whitespace in string", () => { + const str = " "; + const count = 3; + const repeatedStr = repeatStr(str, count); + expect(repeatedStr).toEqual(" "); +}); + +test("repeats unicode characters correctly", () => { + const str = "😊"; + const count = 5; + const repeatedStr = repeatStr(str, count); + expect(repeatedStr).toEqual("😊😊😊😊😊"); +}); + +test("handles empty string", () => { + const str = ""; + const count = 5; + const repeatedStr = repeatStr(str, count); + expect(repeatedStr).toEqual(""); +});