From 28a696fb75a585c0a0b6845ebd495d3edf2c06a2 Mon Sep 17 00:00:00 2001 From: Yonatan Teklemariam Date: Tue, 7 Jul 2026 07:29:16 +0100 Subject: [PATCH 1/8] reverted to original --- Sprint-1/fix/median.js | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/Sprint-1/fix/median.js b/Sprint-1/fix/median.js index b22590bc6..a1e52d497 100644 --- a/Sprint-1/fix/median.js +++ b/Sprint-1/fix/median.js @@ -6,9 +6,31 @@ // or 'list' has mixed values (the function is expected to sort only numbers). function calculateMedian(list) { - const middleIndex = Math.floor(list.length / 2); - const median = list.splice(middleIndex, 1)[0]; - return median; + if (!Array.isArray(list)) { + return null; + } + if (list.length === 0) { + return null; + } + const numbersOnly = list.filter((item) => typeof item === "number"); + if (numbersOnly.length === 0) { + return null; + } + const sorted = [...numbersOnly].sort((a, b) => a - b); + const middleIndex = Math.floor(sorted.length / 2); + if (sorted.length % 2 === 0) { + const median = (sorted[middleIndex - 1] + sorted[middleIndex]) / 2; + return median; + } + return sorted[middleIndex]; } +/* +const middleIndex = Math.floor(list.length / 2); +const median = list.splice(middleIndex, 1)[0]; +return median; +*/ module.exports = calculateMedian; +/* + + */ From cff19af51abaccc5438b6f625a4565711f1ac370 Mon Sep 17 00:00:00 2001 From: Yonatan Teklemariam Date: Tue, 7 Jul 2026 07:45:43 +0100 Subject: [PATCH 2/8] reverted to original --- Sprint-1/fix/median.js | 28 +++------------------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/Sprint-1/fix/median.js b/Sprint-1/fix/median.js index a1e52d497..b22590bc6 100644 --- a/Sprint-1/fix/median.js +++ b/Sprint-1/fix/median.js @@ -6,31 +6,9 @@ // or 'list' has mixed values (the function is expected to sort only numbers). function calculateMedian(list) { - if (!Array.isArray(list)) { - return null; - } - if (list.length === 0) { - return null; - } - const numbersOnly = list.filter((item) => typeof item === "number"); - if (numbersOnly.length === 0) { - return null; - } - const sorted = [...numbersOnly].sort((a, b) => a - b); - const middleIndex = Math.floor(sorted.length / 2); - if (sorted.length % 2 === 0) { - const median = (sorted[middleIndex - 1] + sorted[middleIndex]) / 2; - return median; - } - return sorted[middleIndex]; + const middleIndex = Math.floor(list.length / 2); + const median = list.splice(middleIndex, 1)[0]; + return median; } -/* -const middleIndex = Math.floor(list.length / 2); -const median = list.splice(middleIndex, 1)[0]; -return median; -*/ module.exports = calculateMedian; -/* - - */ From 7f760a0134a769739a55c5c8cb9d2a5d1290fe3a Mon Sep 17 00:00:00 2001 From: Yonatan Teklemariam Date: Wed, 8 Jul 2026 19:38:59 +0100 Subject: [PATCH 3/8] debugged the three functions and fixed them --- Sprint-2/debug/recipe.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Sprint-2/debug/recipe.js b/Sprint-2/debug/recipe.js index 6cbdd22cd..571daee80 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -9,7 +9,14 @@ const recipe = { serves: 2, ingredients: ["olive oil", "tomatoes", "salt", "pepper"], }; - -console.log(`${recipe.title} serves ${recipe.serves} - ingredients: -${recipe}`); +let recipeEntries = Object.entries(recipe); +for (const [reicpeKey, recipeValue] of recipeEntries) { + if (reicpeKey === "ingredients") { + console.log("Ingredients:"); + for (const ingredient of recipeValue) { + console.log(ingredient); + } + } else { + console.log(`${reicpeKey}: ${recipeValue}`); + } +} From 7ed691127c1fdaa567ef80497411f8d750285574 Mon Sep 17 00:00:00 2001 From: Yonatan Teklemariam Date: Wed, 8 Jul 2026 19:46:41 +0100 Subject: [PATCH 4/8] added some comments for highlighting purposes. --- Sprint-2/debug/address.js | 3 ++- Sprint-2/debug/author.js | 5 ++++- Sprint-2/debug/recipe.js | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Sprint-2/debug/address.js b/Sprint-2/debug/address.js index 940a6af83..80c42de5f 100644 --- a/Sprint-2/debug/address.js +++ b/Sprint-2/debug/address.js @@ -12,4 +12,5 @@ const address = { postcode: "XYZ 123", }; -console.log(`My house number is ${address[0]}`); +console.log(`My house number is ${address.houseNumber}`); +// diff --git a/Sprint-2/debug/author.js b/Sprint-2/debug/author.js index 8c2125977..39604252c 100644 --- a/Sprint-2/debug/author.js +++ b/Sprint-2/debug/author.js @@ -2,6 +2,7 @@ // This program attempts to log out all the property values in the object. // But it isn't working. Explain why first and then fix the problem +// Because objects are not iterable, so they need to be converted to arrays so as we can loop through them so, with Object.entries() method as this method converts the objects into key and value pairs within an array. const author = { firstName: "Zadie", @@ -11,6 +12,8 @@ const author = { alive: true, }; -for (const value of author) { +let authorEntries = Object.entries(author); +for (const value of authorEntries) { console.log(value); } +// diff --git a/Sprint-2/debug/recipe.js b/Sprint-2/debug/recipe.js index 571daee80..8c796cbf2 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -1,5 +1,5 @@ // Predict and explain first... - +// so the first recipe.title prints the correct title, the second recipe.serves also prints the correct number of serves but the last recipe in template literals will throw error or perhaps undefined as recipe is an object and needs different method to access it. so basically inorder to access the key and values inside an object, it should be accessed by using object name followed by a dot and one of the values or keys inside it (whatever we want to use). // This program should log out the title, how many it serves and the ingredients. // Each ingredient should be logged on a new line // How can you fix it? From 6ba6fdc2356554d99c425ea5adc5ea6248660e25 Mon Sep 17 00:00:00 2001 From: Yonatan Teklemariam Date: Wed, 8 Jul 2026 20:35:42 +0100 Subject: [PATCH 5/8] implemented the function and wrote the tests, re-run the test to confirm if it is working. all checked passed. --- Sprint-2/implement/contains.js | 9 ++++++++- Sprint-2/implement/contains.test.js | 20 +++++++++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/Sprint-2/implement/contains.js b/Sprint-2/implement/contains.js index cd779308a..8ed7ef395 100644 --- a/Sprint-2/implement/contains.js +++ b/Sprint-2/implement/contains.js @@ -1,3 +1,10 @@ -function contains() {} +function contains(obj, key) { + for (const currentKey of Object.keys(obj)) { + if (currentKey === key) { + return true; + } + } + return false; +} module.exports = contains; diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index 326bdb1f2..2f02912f1 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -10,26 +10,40 @@ as the object contains a key of 'a' E.g. contains({a: 1, b: 2}, 'c') // returns false as the object doesn't contains a key of 'c' */ +console.log("THIS IS THE REAL contains.js FILE"); // Acceptance criteria: // Given a contains function // When passed an object and a property name // Then it should return true if the object contains the property, false otherwise +test("contains both existent and non-existent properties, returns true if existent or false if otherwise", () => { + expect(contains({ z: 2, e: 5 }, "a")).toBe(false); + expect(contains({ y: 6, x: 3, s: 5 }, "s")).toBe(true); +}); // Given an empty object // When passed to contains // Then it should return false -test.todo("contains on empty object returns false"); +test("returns false for an empty object", () => { + expect(contains({}, "a")).toBe(false); +}); // Given an object with properties // When passed to contains with an existing property name // Then it should return true - +test("returns true when object contains the property", () => { + expect(contains({ a: 1, b: 3 }, "a")).toBe(true); +}); // Given an object with properties // When passed to contains with a non-existent property name // Then it should return false - +test("returns false when object does not contain the property", () => { + expect(contains({ w: 1, x: 8 }, "r")).toBe(false); +}); // Given invalid parameters like an array // When passed to contains // Then it should return false or throw an error +test("returns false for invalid parameters like an array", () => { + expect(contains([], "a")).toBe(false); +}); From 0c16c1da1fc70bcd8d335b6e36b6c19753b8ccb9 Mon Sep 17 00:00:00 2001 From: Yonatan Teklemariam Date: Mon, 13 Jul 2026 11:48:54 +0100 Subject: [PATCH 6/8] implemented the functions and added some jest test for the functions --- Sprint-2/implement/lookup.js | 16 +++++++++++-- Sprint-2/implement/querystring.js | 38 +++++++++++++++++++++++++++++-- Sprint-2/implement/tally.js | 22 +++++++++++++++++- 3 files changed, 71 insertions(+), 5 deletions(-) diff --git a/Sprint-2/implement/lookup.js b/Sprint-2/implement/lookup.js index a6746e07f..406dbbf95 100644 --- a/Sprint-2/implement/lookup.js +++ b/Sprint-2/implement/lookup.js @@ -1,5 +1,17 @@ -function createLookup() { - // implementation here +function createLookup(countrycurrency) { + const lookup = {}; + countrycurrency.map(function ([country, currency]) { + console.log(`${country}: ${currency}`); + lookup[country] = currency; + }); + return lookup; } +console.log( + createLookup([ + ["US", "USD"], + ["UAE", "AED"], + ["ERI", "ERN"], + ]) +); module.exports = createLookup; diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 45ec4e5f3..b8e2ae05c 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -3,14 +3,48 @@ function parseQueryString(queryString) { if (queryString.length === 0) { return queryParams; } + const keyValuePairs = queryString.split("&"); for (const pair of keyValuePairs) { - const [key, value] = pair.split("="); - queryParams[key] = value; + if (!pair) continue; // ignore empty pairs + + const index = pair.indexOf("="); + + let rawKey, rawValue; + + if (index === -1) { + rawKey = pair; + rawValue = ""; + } else { + rawKey = pair.slice(0, index); + rawValue = pair.slice(index + 1); + } + + // Replace '+' with space + rawValue = rawValue.replace(/\+/g, " "); + rawKey = rawKey.replace(/\+/g, " "); + + // Decode percent-encoding + rawKey = decodeURIComponent(rawKey); + rawValue = decodeURIComponent(rawValue); + + const key = rawKey; + const value = rawValue; + + // Handle repeated keys → array + if (queryParams.hasOwnProperty(key)) { + if (!Array.isArray(queryParams[key])) { + queryParams[key] = [queryParams[key]]; + } + queryParams[key].push(value); + } else { + queryParams[key] = value; + } } return queryParams; } +console.log(parseQueryString("key=value1&key=value2&key=value3&foo=bar")); module.exports = parseQueryString; diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index f47321812..3a2780e8d 100644 --- a/Sprint-2/implement/tally.js +++ b/Sprint-2/implement/tally.js @@ -1,3 +1,23 @@ -function tally() {} +function tally(arr) { + let counts = {}; + if (arr.length === 0) { + return {}; + } + if (arr !== /^[0-9]A-za-z$/g) { + throw new Error("Please Enter Valid Input!"); + } + + for (const item of arr) { + if (!/^[A-Za-z0-9]$/.test(item)) { + throw new Error("Please Enter Valid Input!"); + } + if (counts.hasOwnProperty(item)) { + counts[item] += 1; + } else { + counts[item] = 1; + } + } + return counts; +} module.exports = tally; From 5ebaa3da37e46e2ef28ac2fab7bd9a2a7e2dedae Mon Sep 17 00:00:00 2001 From: Yonatan Teklemariam Date: Mon, 13 Jul 2026 11:51:12 +0100 Subject: [PATCH 7/8] implemented the tally fiunction and added some jest test for the function --- Sprint-2/implement/lookup.js | 5 ++-- Sprint-2/implement/lookup.test.js | 26 +++++++++++++++++++- Sprint-2/implement/tally.js | 17 ++++--------- Sprint-2/implement/tally.test.js | 41 ++++++++++++++++++++++--------- 4 files changed, 62 insertions(+), 27 deletions(-) diff --git a/Sprint-2/implement/lookup.js b/Sprint-2/implement/lookup.js index 406dbbf95..72b1cbcd5 100644 --- a/Sprint-2/implement/lookup.js +++ b/Sprint-2/implement/lookup.js @@ -1,8 +1,9 @@ function createLookup(countrycurrency) { - const lookup = {}; + const lookup = {}; // lookup object is created empty to store the new keys and values mapped from the function countrycurrency.map(function ([country, currency]) { + // maps every keys and values passed as argument in countrycurrency and stores to the variable names of country and currency console.log(`${country}: ${currency}`); - lookup[country] = currency; + lookup[country] = currency; // this lookup object is then used to store the already mapped keys and values of the countrycurrency }); return lookup; } diff --git a/Sprint-2/implement/lookup.test.js b/Sprint-2/implement/lookup.test.js index 547e06c5a..4242611a4 100644 --- a/Sprint-2/implement/lookup.test.js +++ b/Sprint-2/implement/lookup.test.js @@ -1,6 +1,30 @@ const createLookup = require("./lookup.js"); -test.todo("creates a country currency code lookup for multiple codes"); +test("creates a country currency code lookup object for multiple codes", () => { + const result = createLookup([ + ["US", "USD"], + ["UAE", "AED"], + ["ERI", "ERN"], + ]); + + expect(result).toEqual({ + US: "USD", + UAE: "AED", + ERI: "ERN", + }); +}); + +test("prints country and currency vertically", () => { + console.log = jest.fn(); //mocks the console.log which is used to print out only returned values, but since this test only checks the printed output but not returned values, we do mock the console.log to check the printed output + + createLookup([ + ["US", "USD"], + ["CA", "CAD"], + ]); + + expect(console.log).toHaveBeenCalledWith("US: USD"); + expect(console.log).toHaveBeenCalledWith("CA: CAD"); +}); /* diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index 3a2780e8d..039dc0925 100644 --- a/Sprint-2/implement/tally.js +++ b/Sprint-2/implement/tally.js @@ -1,21 +1,14 @@ function tally(arr) { - let counts = {}; + if (!Array.isArray(arr)) throw new TypeError("Input must be an array"); + const counts = Object.create(null); if (arr.length === 0) { - return {}; + return counts; } - if (arr !== /^[0-9]A-za-z$/g) { - throw new Error("Please Enter Valid Input!"); - } - for (const item of arr) { - if (!/^[A-Za-z0-9]$/.test(item)) { + if (typeof item !== "string" || !/^[A-Za-z0-9]+$/.test(item)) { throw new Error("Please Enter Valid Input!"); } - if (counts.hasOwnProperty(item)) { - counts[item] += 1; - } else { - counts[item] = 1; - } + counts[item] = (counts[item] ?? 0) + 1; } return counts; } diff --git a/Sprint-2/implement/tally.test.js b/Sprint-2/implement/tally.test.js index 2ceffa8dd..667512373 100644 --- a/Sprint-2/implement/tally.test.js +++ b/Sprint-2/implement/tally.test.js @@ -19,16 +19,33 @@ const tally = require("./tally.js"); // Given a function called tally // When passed an array of items // Then it should return an object containing the count for each unique item +describe("tally", () => { + test("Given an array of items, returns counts for each unique item", () => { + const input = ["apple", "banana", "apple", "orange", "banana", "apple"]; + const expected = { apple: 3, banana: 2, orange: 1 }; + expect(tally(input)).toEqual(expected); + }); + // Given an empty array + // When passed to tally + // Then it should return an empty object + test("tally on an empty array returns an empty object", () => { + expect([]).toEqual({}); + }); -// Given an empty array -// When passed to tally -// Then it should return an empty object -test.todo("tally on an empty array returns an empty object"); - -// Given an array with duplicate items -// When passed to tally -// Then it should return counts for each unique item - -// Given an invalid input like a string -// When passed to tally -// Then it should throw an error + // Given an array with duplicate items + // When passed to tally + // Then it should return counts for each unique item + test("Given an array with duplicate items, returns counts for each unique item", () => { + const input = ["x", "x", "y", "x", "y"]; + const expected = { x: 3, y: 2 }; + expect(tally(input)).toEqual(expected); + }); + // Given an invalid input like a string + // When passed to tally + // Then it should throw an error + test("Given an array containing an invalid item, throws an Error", () => { + expect(() => tally(["valid", "hello world"])).toThrow( + "Please Enter Valid Input!" + ); + }); +}); From 096092994d936841f950d18c29fad0d009d8d0bb Mon Sep 17 00:00:00 2001 From: Yonatan Teklemariam Date: Fri, 17 Jul 2026 08:05:40 +0100 Subject: [PATCH 8/8] implemented the key, value conversion and written jest tests all passed --- Sprint-2/interpret/invert.js | 51 +++++++++++++++++++------- Sprint-2/interpret/invert.test.js | 59 +++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 12 deletions(-) create mode 100644 Sprint-2/interpret/invert.test.js diff --git a/Sprint-2/interpret/invert.js b/Sprint-2/interpret/invert.js index bb353fb1f..dcf58ca36 100644 --- a/Sprint-2/interpret/invert.js +++ b/Sprint-2/interpret/invert.js @@ -7,23 +7,50 @@ // E.g. invert({x : 10, y : 20}), target output: {"10": "x", "20": "y"} function invert(obj) { - const invertedObj = {}; - - for (const [key, value] of Object.entries(obj)) { - invertedObj.key = value; + // Validate input type + if (!obj || typeof obj !== "object" || Array.isArray(obj)) { + throw new Error("Input must be an object"); } - return invertedObj; -} + const entries = Object.entries(obj); + if (entries.length === 0) return {}; -// a) What is the current return value when invert is called with { a : 1 } + // take only the values + const values = entries.map(([_, v]) => v); -// b) What is the current return value when invert is called with { a: 1, b: 2 } + // Detect duplicate values + const hasDuplicate = new Set(values).size !== values.length; + if (hasDuplicate) { + throw new Error("Values cannot be duplicated"); + } -// c) What is the target return value when invert is called with {a : 1, b: 2} + const inverted = {}; -// c) What does Object.entries return? Why is it needed in this program? + for (const [key, value] of entries) { + // Validate key and value types + if ( + (typeof key !== "string" && typeof key !== "number") || + (typeof value !== "string" && typeof value !== "number") + ) { + throw new Error("Keys and values must be a string or a number"); + } -// d) Explain why the current return value is different from the target output + inverted[value] = key; + } + + return inverted; +} +module.exports = invert; -// e) Fix the implementation of invert (and write tests to prove it's fixed!) +// a) What is the current return value when invert is called with { a : 1 } +// the current value is 1 +// b) What is the current return value when invert is called with { a: 1, b: 2 } +// the current return value at this call is 2 +// c) What is the target return value when invert is called with {a : 1, b: 2} +// the target return value at this call is to supposed to be 'b' +// d) What does Object.entries return? Why is it needed in this program? +// Object.entries is used because both key and value are needed to be returned from the loop +// e) Explain why the current return value is different from the target output +// it's because the code in the line 13 is only returning single literal property named "key" on every loop iteration, so each assignment overwrites the previous one and the final object ends up with { key: 2 } but the intended inverted output requires using the loop variables as property names (dynamic keys) so each iteration creates a distinct property (for example invertedObj[value] = key), producing separate entries like { 1: 'a', 2: 'b' } instead of repeatedly writing to the same "key" property which the current function is doing +// f) Fix the implementation of invert (and write tests to prove it's fixed!) +// Above is the fixed function diff --git a/Sprint-2/interpret/invert.test.js b/Sprint-2/interpret/invert.test.js new file mode 100644 index 000000000..42c5f7ee9 --- /dev/null +++ b/Sprint-2/interpret/invert.test.js @@ -0,0 +1,59 @@ +const invert = require("./invert"); + +test("inverts a simple object", () => { + const input = { a: "x", b: "y" }; + const expected = { x: "a", y: "b" }; + expect(invert(input)).toEqual(expected); +}); + +test("inverts an object with number values", () => { + const input = { a: 1, b: 2 }; + const expected = { 1: "a", 2: "b" }; + expect(invert(input)).toEqual(expected); +}); + +test("accepts empty string as a key", () => { + const input = { "": "x" }; + const expected = { x: "" }; + expect(invert(input)).toEqual(expected); +}); + +test("returns an empty object when input is empty", () => { + expect(invert({})).toEqual({}); +}); + +test("throws when input is null", () => { + expect(() => invert(null)).toThrow("Input must be an object"); +}); + +test("throws when input is not an object", () => { + expect(() => invert("hello")).toThrow("Input must be an object"); + expect(() => invert(123)).toThrow("Input must be an object"); + expect(() => invert(true)).toThrow("Input must be an object"); +}); + +test("throws when input is an array", () => { + expect(() => invert(["a", "b"])).toThrow("Input must be an object"); +}); + +test("throws when a value is not a string or number", () => { + const obj = { a: {} }; + expect(() => invert(obj)).toThrow( + "Keys and values must be a string or a number" + ); +}); + +test("throws when values are duplicated (string)", () => { + const obj = { a: "x", b: "y", c: "x" }; + expect(() => invert(obj)).toThrow("Values cannot be duplicated"); +}); + +test("throws when numeric values are duplicated", () => { + const obj = { a: 1, b: 2, c: 1 }; + expect(() => invert(obj)).toThrow("Values cannot be duplicated"); +}); + +test("duplicate keys are overwritten by JavaScript before reaching invert()", () => { + const obj = { a: "x", a: "y" }; // JS overwrites first 'a' + expect(invert(obj)).toEqual({ y: "a" }); +});