diff --git a/Sprint-2/debug/address.js b/Sprint-2/debug/address.js index 940a6af83..e62556313 100644 --- a/Sprint-2/debug/address.js +++ b/Sprint-2/debug/address.js @@ -12,4 +12,12 @@ const address = { postcode: "XYZ 123", }; -console.log(`My house number is ${address[0]}`); +// 〰️ Correct way to access the houseNumber property +console.log(`My house number is ${address.houseNumber}`); + +// 〰️ The issue with the code is that it's trying to access address[0], which is not correct because address is an object, and objects in JavaScript don't use numeric indices like arrays. Instead, we should access the houseNumber property directly using the correct key. + +// 〰️ We need to use the correct property key to access the value. Since the key for the house number in the address object is "houseNumber", we should use address.houseNumber to access it. + +// 〰️ address.houseNumber accesses the houseNumber property of the address object. +// 〰️ The result will correctly log: "My house number is 42" \ No newline at end of file diff --git a/Sprint-2/debug/author.js b/Sprint-2/debug/author.js index 8c2125977..63640d57b 100644 --- a/Sprint-2/debug/author.js +++ b/Sprint-2/debug/author.js @@ -11,6 +11,23 @@ const author = { alive: true, }; -for (const value of author) { +// 〰️ 1.Using for...in: +for (const key in author) { + console.log(author[key]); +} + +// 〰️ 2.Using Object.values(): +/* +for (const value of Object.values(author)) { console.log(value); } +*/ + +// 〰️ The issue here is that JavaScript objects are not iterable using the for...of loop. This loop is designed to work with iterable objects like arrays, strings, maps, and sets, but not plain objects. +// 〰️ Trying to use for...of, result will be TypeError or no output. + +// 〰️ How we can fix it: +// 〰️ We can use the for...in loop or Object.values(): +// 〰️ 1. Using for...in: This loop iterates over the keys of the object. +// 〰️ 2. Using Object.values(): This returns an array of property values, allowing you to use for...of + diff --git a/Sprint-2/debug/recipe.js b/Sprint-2/debug/recipe.js index 6cbdd22cd..73114a60a 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -12,4 +12,13 @@ const recipe = { console.log(`${recipe.title} serves ${recipe.serves} ingredients: -${recipe}`); +${recipe.ingredients.join("\n")}`); + + +// 〰️ When the code logs ${recipe}, JavaScript converts the entire object into a string using its default toString() method, which returns [object Object]. +// 〰️ This happens because JavaScript doesn't know how to format the object automatically. +// 〰️ Additionally, the code doesn't iterate over the ingredients array, so the list isn't displayed properly. + +// 〰️ We can fix it to use recipe.ingredients.join("\n") to join the ingredients with new lines, ensuring each appears on its own line. + +// 〰️ recipe.ingredients.join("\n") converts the array into a string with each ingredient on a new line, creating a clean and readable output. \ No newline at end of file diff --git a/Sprint-2/implement/contains.js b/Sprint-2/implement/contains.js index cd779308a..19e9555ed 100644 --- a/Sprint-2/implement/contains.js +++ b/Sprint-2/implement/contains.js @@ -1,3 +1,19 @@ -function contains() {} +function contains(obj, key) { + // 〰️ Checking if the input is a valid object or not null or an array + if (typeof obj !== "object" || obj === null || Array.isArray(obj)) { + return false; + } + // 〰️ Using hasOwnProperty to check if the key exists directly on the object + return obj.hasOwnProperty(key); +} + +console.log(contains({a: 1, b: 2}, 'a')); +console.log(contains({ name: "Ali", age: 25 }, 'age')); +console.log(contains({ x: 10, y: 20 }, 'y')); + +console.log(contains({a: 1, b: 2}, 'c')); +console.log(contains({}, 'a')); + + module.exports = contains; diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index 326bdb1f2..142f508df 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -16,20 +16,45 @@ as the object doesn't contains a key of 'c' // 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("returns true if the object contains the property, false otherwise", () => { + expect(contains({ a: 1, b: 2 }, "a")).toBe(true); + expect(contains({ a: 1, b: 2 }, "c")).toBe(false); +}); // 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("returns true if the property exists in the object", () => { + expect(contains({ x: 10, y: 20 }, "x")).toBe(true); // 'x' var +}); // Given an object with properties // When passed to contains with a non-existent property name // Then it should return false +test("contains with non-existent property name 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 with invalid parameters like an array returns false", () => { + expect(contains([1, 2, 3], "a")).toBe(false); + }); + +// 〰️ Test if contains returns false when null is passed as the object. +test("contains with null returns false", () => { + expect(contains(null, "a")).toBe(false); +}); + +// 〰️ Test if contains returns false when a non-object value is passed. +test("contains with a non-object value", () => { + expect(contains(42, 'a')).toBe(false); +}); \ No newline at end of file diff --git a/Sprint-2/implement/lookup.js b/Sprint-2/implement/lookup.js index a6746e07f..c24e0c713 100644 --- a/Sprint-2/implement/lookup.js +++ b/Sprint-2/implement/lookup.js @@ -1,5 +1,22 @@ -function createLookup() { - // implementation here +// 〰️ Function to create a lookup object from an array of country-currency pairs +function createLookup(countryCurrencyPairs) { + // 〰️ Use Object.fromEntries to transform the array into an object + return Object.fromEntries(countryCurrencyPairs); } +console.log(createLookup([['US', 'USD'], ['CA', 'CAD']])); +console.log(createLookup([['IN', 'INR'], ['JP', 'JPY']])); +console.log(createLookup([])); + module.exports = createLookup; + + +// 〰️ Explanation of Object.fromEntries(): +// 〰️ Object.fromEntries() is a built-in JavaScript method that converts an array of key-value pairs into an object. It's particularly useful when you have data in array format and want to convert it into an object without using loops or more complex methods like reduce(). + +// 〰️ How It Works: +// 〰️ 1. It expects an array where each element is a two-item array representing [key, value]. +// 〰️ 2.It processes each pair and adds the key-value pair to a new object. +// 〰️ 3.It returns the resulting object. + +// 〰️ Since createLookup takes an array of pairs, using Object.fromEntries() makes the conversion simple and clean. diff --git a/Sprint-2/implement/lookup.test.js b/Sprint-2/implement/lookup.test.js index 547e06c5a..ca6b5a4e4 100644 --- a/Sprint-2/implement/lookup.test.js +++ b/Sprint-2/implement/lookup.test.js @@ -1,6 +1,35 @@ const createLookup = require("./lookup.js"); -test.todo("creates a country currency code lookup for multiple codes"); +test("creates a country-currency lookup object from an array of pairs", () => { + // 〰️ Arrange: Define input and expected output + const input = [['US', 'USD'], ['CA', 'CAD']]; + const expectedOutput = { + 'US': 'USD', + 'CA': 'CAD' + }; + + // 〰️ Act: Call the function with input + const result = createLookup(input); + + // 〰️ Assert: Check if the result matches expected output + expect(result).toEqual(expectedOutput); +}); + +test("returns an empty object if input is empty", () => { + expect(createLookup([])).toEqual({}); + }); + + +test("handles multiple entries correctly", () => { + const input = [['IN', 'INR'], ['JP', 'JPY'], ['GB', 'GBP']]; + const expectedOutput = { + 'IN': 'INR', + 'JP': 'JPY', + 'GB': 'GBP' + }; + expect(createLookup(input)).toEqual(expectedOutput); + }); + /* diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 45ec4e5f3..fb73da6a2 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -1,16 +1,53 @@ function parseQueryString(queryString) { const queryParams = {}; - if (queryString.length === 0) { + + // 〰️ Check for null, undefined, or non-string input + if (!queryString || typeof queryString !== "string") { return queryParams; } + + // 〰️ Split the query string into key-value pairs using '&' const keyValuePairs = queryString.split("&"); for (const pair of keyValuePairs) { - const [key, value] = pair.split("="); - queryParams[key] = value; + // 〰️ Split each pair by the first '=' and keep remaining as the value + const [rawKey, ...rawValue] = pair.split("="); + + // 〰️ Decode keys and values safely + // 〰️ Using decodeURIComponent() to convert percent-encoded strings into readable text. + // 〰️ Example: 'John%20Doe' -> 'John Doe', '5%25' -> '5%' + // 〰️ rawKey || "" ensures that if rawKey is undefined, null, or empty, it returns to an empty string. + const key = decodeURIComponent(rawKey || ""); + const value = decodeURIComponent(rawValue.join("=") || ""); + // 〰️ Handle duplicate keys by storing values in an array + if (key in queryParams) { + if (Array.isArray(queryParams[key])) { + queryParams[key].push(value); // 〰️ Add to existing array + } else { + queryParams[key] = [queryParams[key], value]; // 〰️ Convert to array + } + } else { + queryParams[key] = value; // 〰️ Add new key-value pair + } } return queryParams; } +console.log(parseQueryString("equation=x=y+1")); +console.log(parseQueryString("key1=value1&key2=value2")); +console.log(parseQueryString("equation=x=y+1&other=abc=123")); +console.log(parseQueryString("name=John%20Doe&value=5%25")); +console.log(parseQueryString("first%20name=Alice")); +console.log(parseQueryString("%26key%3D=value%3F")); +console.log(parseQueryString("equation=x%3Dy%2B1")); +console.log(parseQueryString("specialChars=!%40%23%24%25%5E%26*()")); +console.log(parseQueryString("emptyKey=")); +console.log(parseQueryString("=emptyValue")); +console.log(parseQueryString("multiple=values&key=value")); +console.log(parseQueryString(null)); +console.log(parseQueryString(undefined)); +console.log(parseQueryString("")); +console.log(parseQueryString("key=value1&key=value2")); + module.exports = parseQueryString; diff --git a/Sprint-2/implement/querystring.test.js b/Sprint-2/implement/querystring.test.js index 3e218b789..c3f0c41a8 100644 --- a/Sprint-2/implement/querystring.test.js +++ b/Sprint-2/implement/querystring.test.js @@ -10,3 +10,42 @@ test("parses querystring values containing =", () => { "equation": "x=y+1", }); }); + +test("parses multiple key-value pairs", () => { + expect(parseQueryString("key1=value1&key2=value2")).toEqual({ + key1: "value1", + key2: "value2", + }); +}); + +test("parses querystring values containing multiple '='", () => { + expect(parseQueryString("equation=x=y+1&other=abc=123")).toEqual({ + equation: "x=y+1", + other: "abc=123", + }); +}); + +test('handles empty keys and values', () => { + expect(parseQueryString("emptyKey=")).toEqual({ emptyKey: '' }); + expect(parseQueryString("=emptyValue")).toEqual({ '': 'emptyValue' }); +}); + +test('parses simple query strings', () => { + expect(parseQueryString("name=John%20Doe&value=5%25")).toEqual({ name: 'John Doe', value: '5%' }); +}); + +test('handles special characters', () => { + expect(parseQueryString("specialChars=!%40%23%24%25%5E%26*()")).toEqual({ specialChars: '!@#$%^&*()' }); +}); + +test('returns empty object for null, undefined, or empty strings', () => { + expect(parseQueryString(null)).toEqual({}); + expect(parseQueryString(undefined)).toEqual({}); + expect(parseQueryString("")).toEqual({}); +}); + +test("handles duplicate keys by storing values in an array", () => { + expect(parseQueryString("key=value1&key=value2")).toEqual({ + key: ["value1", "value2"], + }); +}); \ No newline at end of file diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index f47321812..f8e030bf7 100644 --- a/Sprint-2/implement/tally.js +++ b/Sprint-2/implement/tally.js @@ -1,3 +1,28 @@ -function tally() {} +function tally(arr) { + // 〰️ Check if input is an array + if (!Array.isArray(arr)) { + throw new Error("Input must be an array") + } + + // 〰️ Create an empty object to store counts + let counts = {}; + + // 〰️ Loop through the array and count items + for (let item of arr) { + if (counts[item]) { + counts[item] += 1; // 〰️ Increase count if item exists + } else { + counts[item] = 1; // 〰️ Initialize count if item is new + } + } + + return counts; // 〰️ Return the final counts object +} + +console.log(tally(['a'])); +console.log(tally(['a', 'a', 'a'])); +console.log(tally(['a', 'a', 'b', 'c'])); +console.log(tally([])); +// console.log(tally('a')); module.exports = tally; diff --git a/Sprint-2/implement/tally.test.js b/Sprint-2/implement/tally.test.js index 2ceffa8dd..790138e39 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 +test("tally on an array with one item", ()=> { + expect(tally(["a"])).toEqual({ a: 1 }); +}); // 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("tally on an array with duplicate items", ()=> { + expect(tally(["a", "a", "a"])).toEqual({ a: 3 }); +}); + +// 〰️ Given an array with different items +test("tally on an array with different items", ()=> { + expect(tally(["a", "a", "b", "c", "d"])).toEqual({ a: 2, b: 1, c: 1, d: 1 }); +}); // Given an invalid input like a string // When passed to tally // Then it should throw an error +test("throws an error when input is not an array", ()=> { + expect(()=> tally("not an array")).toThrow("Input must be an array"); +}); + diff --git a/Sprint-2/interpret/invert.js b/Sprint-2/interpret/invert.js index bb353fb1f..58196f605 100644 --- a/Sprint-2/interpret/invert.js +++ b/Sprint-2/interpret/invert.js @@ -9,21 +9,43 @@ function invert(obj) { const invertedObj = {}; + // 〰️ We should use Object.entries to get key-value pairs and swap them for (const [key, value] of Object.entries(obj)) { - invertedObj.key = value; + invertedObj[value] = key; // 〰️ Swap the key and value } return invertedObj; } +console.log(invert({x : 10, y : 20})); + // a) What is the current return value when invert is called with { a : 1 } +// 〰️ The current implementation has a mistake when trying to swap keys and values. Specifically, it uses invertedObj.key = value;, which means it's always assigning the value to the property key of invertedObj. +// 〰️ This is wrong because we want the key to be the new value and the value to be the new key. +// 〰️ When the current function is called with { a : 1 }: +// 〰️ The loop would run once with key = 'a' and value = 1. +// 〰️ The code would execute invertedObj.key = 1, which creates a key key with value 1, and thus the result is { key: 1 }. +// 〰️So, the current return value for { a : 1 } is: { "key": 1 } // b) What is the current return value when invert is called with { a: 1, b: 2 } +// 〰️ When we call invert({ a: 1, b: 2 }) with the current code: +// 〰️ The first iteration would set invertedObj.key = 1, resulting in { key: 1 }. +// 〰️ The second iteration would also try to assign to invertedObj.key = 2, which overwrites the previous key-value pair, so the final result is { key: 2 }. +// 〰️ The current return value for { a: 1, b: 2 } is: { "key": 2 } // c) What is the target return value when invert is called with {a : 1, b: 2} +// 〰️ The target return value should swap the keys and values, meaning the key a should become 1 and the key b should become 2. So the expected output for { a: 1, b: 2 } is: { "1": "a", "2": "b" } // c) What does Object.entries return? Why is it needed in this program? +// 〰️ Object.entries returns an array of key-value pairs from an object. For example: +// 〰️ Object.entries({ a: 1, b: 2 }) would return [["a", 1], ["b", 2]]. +// 〰️ It is used in the program to loop over each key-value pair of the object. Without it, there would be no easy way to access both the key and the value at the same time inside the loop. // d) Explain why the current return value is different from the target output +// 〰️ The current return value is incorrect because the code mistakenly assigns value to the key property of the invertedObj. It does not actually swap the key and value. Instead of using invertedObj.key = value;, we should use invertedObj[value] = key; to properly swap the key-value pairs. // e) Fix the implementation of invert (and write tests to prove it's fixed!) +// 〰️ Tests to prove it's fixed +console.log(invert({ a: 1 })); // 〰️ Expected output: { "1": "a" } +console.log(invert({ a: 1, b: 2 })); // 〰️ Expected output: { "1": "a", "2": "b" } +console.log(invert({ x: 10, y: 20 })); // 〰️ Expected output: { "10": "x", "20": "y" }