diff --git a/Sprint-2/debug/address.js b/Sprint-2/debug/address.js index 940a6af83..36d2f865d 100644 --- a/Sprint-2/debug/address.js +++ b/Sprint-2/debug/address.js @@ -12,4 +12,4 @@ 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..f0b1a2dbb 100644 --- a/Sprint-2/debug/author.js +++ b/Sprint-2/debug/author.js @@ -11,6 +11,17 @@ const author = { alive: true, }; -for (const value of author) { +// Prediction: +// The code will not log the property values of the author object as intended. +// This causes the error because author is not iterable. + + // Explanation: + +// The original code likely used a for...in loop which iterates over the keys of the object, not the values. +// Therefore, logging the key would not give the desired property values. +// By using Object.values(author), we can directly iterate over the values of the object. + +// Fixed code: +for (const value of Object.values(author)) { console.log(value); -} +} \ No newline at end of file diff --git a/Sprint-2/debug/recipe.js b/Sprint-2/debug/recipe.js index 6cbdd22cd..d9d7678ae 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -10,6 +10,16 @@ const recipe = { ingredients: ["olive oil", "tomatoes", "salt", "pepper"], }; + +// Prediction: +// The code will not log the ingredients correctly because it is trying to log the entire recipe object instead of just the ingredients array. +// This will result in an output that includes the object structure rather than the individual ingredients. + +// Explanation: +// The template literal is incorrectly using `${recipe}` which outputs the whole object instead of iterating over the ingredients array. +// To fix this, we need to access the ingredients property and format it correctly, possibly by joining the array elements with new line characters. + +// Fixed code: console.log(`${recipe.title} serves ${recipe.serves} ingredients: -${recipe}`); +${recipe.ingredients.join('\n')}`); \ No newline at end of file diff --git a/Sprint-2/implement/contains.js b/Sprint-2/implement/contains.js index cd779308a..689b70b9b 100644 --- a/Sprint-2/implement/contains.js +++ b/Sprint-2/implement/contains.js @@ -1,3 +1,11 @@ -function contains() {} +function contains(obj, key) { + // Check if the first argument is a valid object + if (typeof obj !== "object" || obj === null) { + return false; + } + + // Check if the key exists in the object (only own properties) + return Object.prototype.hasOwnProperty.call(obj, key); +} module.exports = contains; diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index 326bdb1f2..7925af3ab 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -17,19 +17,55 @@ as the object doesn't contains a key of 'c' // When passed an object and a property name // Then it should return true if the object contains the property, false otherwise + // Given an empty object // When passed to contains // Then it should return false -test.todo("contains on empty object returns false"); +test("contains on empty object returns false", () => { + 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("contains on object with existing property returns true", () => { + expect(contains({ a: 1, b: 2 }, "a")).toBe(true); +}); // Given an object with properties // When passed to contains with a non-existent property name // Then it should return false +test("contains on object with non-existent property returns false", () => { + expect(contains({ a: 1, b: 2 }, "c")).toBe(false); +}); // Given invalid parameters like an array // When passed to contains // Then it should return false or throw an error +test("contains on invalid parameters returns false", () => { + expect(contains([], "a")).toBe(false); +}); + +// === NEW TESTS ADDED BELOW === + +// Test for inherited properties (should return false) +test("contains should return false for inherited properties", () => { + expect(contains({a: 1, b: 2}, 'toString')).toBe(false); +}); + +// Test with arrays (should work properly) +test("contains should work with arrays", () => { + expect(contains(["a", "b", "c"], "0")).toBe(true); + expect(contains(["a", "b", "c"], "1")).toBe(true); + expect(contains(["a", "b", "c"], "2")).toBe(true); + expect(contains(["a", "b", "c"], "3")).toBe(false); // check index that does not exist +}); + + +// Test with array as object +test("contains should handle arrays correctly", () => { + const arr = ['x', 'y', 'z']; + expect(contains(arr, '0')).toBe(true); + expect(contains(arr, '2')).toBe(true); + expect(contains(arr, '3')).toBe(false); +}); \ No newline at end of file diff --git a/Sprint-2/implement/lookup.js b/Sprint-2/implement/lookup.js index a6746e07f..b30c2f990 100644 --- a/Sprint-2/implement/lookup.js +++ b/Sprint-2/implement/lookup.js @@ -1,5 +1,13 @@ -function createLookup() { - // implementation here +function createLookup(countryCurrencyPairs) { + const lookup = {}; + + for (const pair of countryCurrencyPairs) { + const countryCode = pair[0]; + const currencyCode = pair[1]; + lookup[countryCode] = currencyCode; + } + + return lookup; } module.exports = createLookup; diff --git a/Sprint-2/implement/lookup.test.js b/Sprint-2/implement/lookup.test.js index 547e06c5a..75d077a31 100644 --- a/Sprint-2/implement/lookup.test.js +++ b/Sprint-2/implement/lookup.test.js @@ -1,6 +1,21 @@ const createLookup = require("./lookup.js"); -test.todo("creates a country currency code lookup for multiple codes"); +test("creates a country currency code lookup for multiple codes", () => { + // Given: An array of arrays representing country code and currency code pairs + const countryCurrencyPairs = [ + ["US", "USD"], + ["CA", "CAD"], + ]; + + // When: createLookup function is called with the country-currency array + const result = createLookup(countryCurrencyPairs); + + // Then: It should return an object where keys are country codes and values are currency codes + expect(result).toEqual({ + US: "USD", + CA: "CAD", + }); +}); /* diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 45ec4e5f3..de7074099 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -1,13 +1,29 @@ function parseQueryString(queryString) { const queryParams = {}; - if (queryString.length === 0) { + + if (!queryString || queryString.length === 0) { return queryParams; } + + // Remove leading '?' if present + if (queryString.startsWith("?")) { + queryString = queryString.substring(1); + } + const keyValuePairs = queryString.split("&"); for (const pair of keyValuePairs) { - const [key, value] = pair.split("="); - queryParams[key] = value; + // Only split on the first '=' to handle values containing '=' + const equalsIndex = pair.indexOf("="); + if (equalsIndex === -1) { + // Handle keys without values + const key = decodeURIComponent(pair); + queryParams[key] = ""; + } else { + const key = decodeURIComponent(pair.substring(0, equalsIndex)); + const value = decodeURIComponent(pair.substring(equalsIndex + 1)); + queryParams[key] = value; + } } return queryParams; diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index f47321812..91b726034 100644 --- a/Sprint-2/implement/tally.js +++ b/Sprint-2/implement/tally.js @@ -1,3 +1,22 @@ -function tally() {} +function tally(items) { + + if (!Array.isArray(items)) { + throw new Error("Input must be an array"); + } + + const result = Object.create(null); + + + for (const item of items) { + if (result[item]) { + result[item] += 1; + } else { + result[item] = 1; + } + } + + return result; + +} module.exports = tally; diff --git a/Sprint-2/implement/tally.test.js b/Sprint-2/implement/tally.test.js index 2ceffa8dd..69a328501 100644 --- a/Sprint-2/implement/tally.test.js +++ b/Sprint-2/implement/tally.test.js @@ -23,12 +23,20 @@ const tally = require("./tally.js"); // 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"); +test("tally on an empty array returns an empty object", () => { + expect(tally([])).toEqual({}); +}); // Given an array with duplicate items // When passed to tally // Then it should return counts for each unique item +test("returns correct counts for array with duplicate items", () => { + expect(tally(["a", "a", "b", "c"])).toEqual({ a: 2, b: 1, c: 1 }); +}); // Given an invalid input like a string // When passed to tally // Then it should throw an error +test("throws an error for invalid input (not an array)", () => { + expect(() => tally("a")).toThrow("Input must be an array"); +}); diff --git a/Sprint-2/interpret/invert.js b/Sprint-2/interpret/invert.js index bb353fb1f..76cd7825d 100644 --- a/Sprint-2/interpret/invert.js +++ b/Sprint-2/interpret/invert.js @@ -6,24 +6,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; - } - return invertedObj; -} // a) What is the current return value when invert is called with { a : 1 } +// Object.entries({ a: 1 }) → [["a", 1]] +// The loop runs once with key = "a" and value = 1 +// Inside the loop: +// invertedObj.key = value; +// creates a property literally named "key", not "a" +// So the result is { key: 1 }. +// Answer: { key: 1 } // b) What is the current return value when invert is called with { a: 1, b: 2 } +// Object.entries({ a: 1, b: 2 }) → [["a", 1], ["b", 2]] +// First loop: invertedObj.key = 1 → { key: 1 } +// Second loop: invertedObj.key = 2 → overwrites previous value +// Final result: { key: 2 } +// Answer: { key: 2 } // c) What is the target return value when invert is called with {a : 1, b: 2} +// { "1": "a", "2": "b" } +// The goal is to swap keys and values, so keys become values and values become keys // c) What does Object.entries return? Why is it needed in this program? +// Object.entries(obj) returns an array of [key, value] pairs from the object +// Object.entries({ a: 1, b: 2 }); +// returns [["a", 1], ["b", 2]] +// It’s needed so that the loop: +// for (const [key, value] of Object.entries(obj)) +// can easily access both the key and value in one step. // d) Explain why the current return value is different from the target output +// The current code uses invertedObj.key = value, which creates a property literally named "key". +// To use the variable key dynamically, you must use bracket notation: +// invertedObj[value] = key; // e) Fix the implementation of invert (and write tests to prove it's fixed!) + +function invert(obj) { + const invertedObj = {}; + for (const [key, value] of Object.entries(obj)) { + invertedObj[value] = key; + } + return invertedObj; +} + +module.exports = invert; + diff --git a/Sprint-2/stretch/count-words.js b/Sprint-2/stretch/count-words.js index 8e85d19d7..b1f445d7c 100644 --- a/Sprint-2/stretch/count-words.js +++ b/Sprint-2/stretch/count-words.js @@ -26,3 +26,39 @@ 3. Order the results to find out which word is the most common in the input */ + + + +function countWords(str) { + // Use Object.create(null) to avoid inherited properties + const wordCounts = Object.create(null); + + if (!str || str.trim() === "") { + return wordCounts; + } + + // Split into words and handle punctuation/case + const words = str.split(/\s+/); + + for (const word of words) { + // Remove punctuation and convert to lowercase + const cleanWord = word + .toLowerCase() + .replace(/[.,!?]/g, "") + .trim(); + + // Skip empty strings + if (cleanWord === "") continue; + + // Count the word + if (wordCounts[cleanWord]) { + wordCounts[cleanWord] += 1; + } else { + wordCounts[cleanWord] = 1; + } + } + + return wordCounts; +} + +module.exports = countWords; \ No newline at end of file