diff --git a/Sprint-2/debug/address.js b/Sprint-2/debug/address.js index 940a6af83..f04f731c8 100644 --- a/Sprint-2/debug/address.js +++ b/Sprint-2/debug/address.js @@ -1,5 +1,5 @@ // Predict and explain first... - +// this code should log out undefined because even the object "address" is well defined but the property "houseNumber" is not being correctly accessed in the last line of code. // This code should log out the houseNumber from the address object // but it isn't working... // Fix anything that isn't working @@ -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..e9fa5b762 100644 --- a/Sprint-2/debug/author.js +++ b/Sprint-2/debug/author.js @@ -1,5 +1,5 @@ // Predict and explain first... - +// it should log out an error because the for statement is not correctly expressed and the prorties of the object are accessed instead of the values. // 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 @@ -11,6 +11,6 @@ const author = { alive: true, }; -for (const value of author) { - console.log(value); +for (const value in author) { + console.log(author[value]); } diff --git a/Sprint-2/debug/recipe.js b/Sprint-2/debug/recipe.js index 6cbdd22cd..259f9a2ba 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -1,5 +1,5 @@ // Predict and explain first... - +// it should not log out the ingredients property's values because the ingredients are stored in an array and the code is not correctly accessing the array elements. // 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? @@ -12,4 +12,4 @@ const recipe = { console.log(`${recipe.title} serves ${recipe.serves} ingredients: -${recipe}`); +${recipe.ingredients.join("\n")}`); diff --git a/Sprint-2/implement/contains.js b/Sprint-2/implement/contains.js index cd779308a..a49b5cf01 100644 --- a/Sprint-2/implement/contains.js +++ b/Sprint-2/implement/contains.js @@ -1,3 +1,8 @@ -function contains() {} +function contains(obj, name) { + if (typeof obj !== "object" || obj === null || Array.isArray(obj)) { + return false; + } + return Object.hasOwn(obj, name); +} module.exports = contains; diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index 326bdb1f2..9acbfd464 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -20,16 +20,36 @@ as the object doesn't contains a key of 'c' // Given an empty object // When passed to contains // Then it should return false -test.todo("contains on empty object returns false"); +test("given an empty object, returns false", () => { + expect(contains({}, "x")).toEqual(false); +}); // Given an object with properties // When passed to contains with an existing property name // Then it should return true +test("given an object with properties, returns true when passed to contains with an existing property name", () => { + expect( + contains({ gitName: "djebsoft", position: "cyf trainee" }, "gitName") + ).toEqual(true); +}); // Given an object with properties // When passed to contains with a non-existent property name // Then it should return false +test("given an object with properties, returns false when passed to contains with a non-existent property name", () => { + expect( + contains({ gitName: "djebsoft", position: "cyf trainee" }, "age") + ).toEqual(false); +}); // Given invalid parameters like an array // When passed to contains // Then it should return false or throw an error +// "length" is perfect for the test because it's real property on the array itself +test("given invalid parameters (array), returns false or throws an error", () => { + expect(contains(["gitName", "age", "position"], "length")).toBe(false); +}); + +test("given invalid parameters (like null or undefined), returns false or throws an error", () => { + expect(contains(null, "gitName")).toEqual(false); +}); diff --git a/Sprint-2/implement/lookup.js b/Sprint-2/implement/lookup.js index a6746e07f..a5bb64409 100644 --- a/Sprint-2/implement/lookup.js +++ b/Sprint-2/implement/lookup.js @@ -1,5 +1,24 @@ -function createLookup() { - // implementation here +// the first element is the country code +// the second array is the currency code of the country +// the function should return an object +// the keys of the object should be the first element of each inner array (country code) +// the values of the object should be the second element of each inner array (currency code) + +function createLookup(countryCurrencyPairs) { + // check if the argument is an array of arrays + if ( + !Array.isArray(countryCurrencyPairs) || + // check if each inner array is an array. + countryCurrencyPairs.some((pair) => !Array.isArray(pair)) || + // check if each inner array has two elements. + countryCurrencyPairs.some((pair) => pair.length !== 2) + // we can also use index to check the pair is an array as follows: + // for (let i = 0; i < countryCurrencyPairs.length; i++) + // return Array.isArray(countryCurrencyPairs[i]) + ) { + throw new Error("Invalid input: expected an array of arrays"); + } + return Object.fromEntries(countryCurrencyPairs); } module.exports = createLookup; diff --git a/Sprint-2/implement/lookup.test.js b/Sprint-2/implement/lookup.test.js index 547e06c5a..1909a7039 100644 --- a/Sprint-2/implement/lookup.test.js +++ b/Sprint-2/implement/lookup.test.js @@ -1,6 +1,18 @@ -const createLookup = require("./lookup.js"); +const Lookup = require("./lookup.js"); -test.todo("creates a country currency code lookup for multiple codes"); +test("creates a country currency code lookup for multiple codes", () => { + const input = [ + ["DZ", "DZD"], + ["CA", "CAD"], + ["GB", "GBP"], + ]; + const result = { + DZ: "DZD", + CA: "CAD", + GB: "GBP", + }; + expect(Lookup(input)).toEqual(result); +}); /* @@ -24,7 +36,7 @@ Example Given: [['US', 'USD'], ['CA', 'CAD']] When -createLookup(countryCurrencyPairs) is called +Lookup(countryCurrencyPairs) is called Then It should return: @@ -33,3 +45,37 @@ It should return: 'CA': 'CAD' } */ +// Given an invalid input (not an array of arrays), +test('given string input throw "Invalid input: expected an array of arrays"', () => { + let input = "invalid input: expected an array of arrays"; + expect(() => Lookup(input)).toThrow( + "Invalid input: expected an array of arrays" + ); +}); + +// Given an array where its elements are not arrays, +test('given array with non-array elements throw "Invalid input: expected an array of arrays"', () => { + let input = [["US", "USD"], "CA"]; + expect(() => Lookup(input)).toThrow( + "Invalid input: expected an array of arrays" + ); +}); + +// Given an array where its elements are arrays with more than two elements, +test('given arrays with too many elements throw "Invalid input: expected an array of arrays"', () => { + const input = [ + ["US", "USD", "flag"], + ["CA", "CAD"], + ]; + expect(() => Lookup(input)).toThrow( + "Invalid input: expected an array of arrays" + ); +}); + +// Given an array where its elements are arrays with less than two elements, +test('given arrays with too few elements throw "Invalid input: expected an array of arrays"', () => { + const input = [["US", "USD"], ["CA"]]; + expect(() => Lookup(input)).toThrow( + "Invalid input: expected an array of arrays" + ); +}); diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 45ec4e5f3..cad8592a6 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -1,13 +1,20 @@ +// the bug in the code is that it does not handle cases where the query string has two = signs or more between two parameters. +// in this case we make sure to split the string only at the first = sign. +// so we use the spread operator. function parseQueryString(queryString) { const queryParams = {}; + if (queryString === null || queryString === undefined) { + return "invalid query string"; + } if (queryString.length === 0) { return queryParams; } const keyValuePairs = queryString.split("&"); for (const pair of keyValuePairs) { - const [key, value] = pair.split("="); - queryParams[key] = value; + const [key, ...value] = pair.split("="); + // we convert the value array back to a string and we get back the = signs by using it as separator. + queryParams[key] = value.join("="); } return queryParams; diff --git a/Sprint-2/implement/querystring.test.js b/Sprint-2/implement/querystring.test.js index 3e218b789..d9171b8c9 100644 --- a/Sprint-2/implement/querystring.test.js +++ b/Sprint-2/implement/querystring.test.js @@ -3,10 +3,85 @@ // Below is one test case for an edge case the implementation doesn't handle well. // Fix the implementation for this test, and try to think of as many other edge cases as possible - write tests and fix those too. -const parseQueryString = require("./querystring.js") +const parseQueryString = require("./querystring.js"); +// testing when the query string with a value contains = signs test("parses querystring values containing =", () => { expect(parseQueryString("equation=x=y+1")).toEqual({ - "equation": "x=y+1", + equation: "x=y+1", + }); +}); + +// testing when the query string has multiple ampersands +test("parses query string with multiple parameters", () => { + expect(parseQueryString("name=Ben&age=45&city=new%20city")).toEqual({ + name: "Ben", + age: "45", + city: "new%20city", + }); +}); + +// testing when the query string has two ampersands in a row +test("parses query string with two ampersands in a row", () => { + expect(parseQueryString("name=Ben&&age=45")).toEqual({ + name: "Ben", + "": "", + age: "45", + }); +}); + +// testing when the query string has no ampersands +test("parses query string with no ampersands", () => { + expect(parseQueryString("name=Ben")).toEqual({ + name: "Ben", + }); +}); + +// testing when the query string has no ampersands and no equal signs +test("parses query string with no ampersands and no equal signs", () => { + expect(parseQueryString("name")).toEqual({ + name: "", + }); +}); + +// testing when the query string is empty +test("parses query string with empty value", () => { + expect(parseQueryString("")).toEqual({}); +}); + +// testing when the query string is null +test("parses query string with null value", () => { + expect(parseQueryString(null)).toEqual("invalid query string"); +}); + +// testing when the query string is undefined +test("parses query string with undefined value", () => { + expect(parseQueryString(undefined)).toEqual("invalid query string"); +}); + +// testing when a pair ends with equal sign (no value) +test("parses query string with no equal sign", () => { + expect(parseQueryString("name=Ben&age=&city=")).toEqual({ + name: "Ben", + age: "", + city: "", + }); +}); + +// testing when a pair starts with equal sign (no key) +test("parses query string starting with equal sign", () => { + expect(parseQueryString("name=Ben&=45")).toEqual({ + name: "Ben", + "": "45", + }); +}); + +// testing when a pair has no equal sign (no value and no key) +// the pair will be the key and the value will be an empty string +test("parses query string with no equal sign", () => { + expect(parseQueryString("name&age=45&city")).toEqual({ + name: "", + age: "45", + city: "", }); }); diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index f47321812..ab2ba96bc 100644 --- a/Sprint-2/implement/tally.js +++ b/Sprint-2/implement/tally.js @@ -1,3 +1,13 @@ -function tally() {} - +function tally(arr) { + if (!Array.isArray(arr)) { + throw new Error("invalid input"); + } + // using Object.create(null) to create a plain object without prototype + let result = Object.create(null); + for (let i = 0; i <= arr.length - 1; i++) { + let item = arr[i]; + result[item] = (result[item] || 0) + 1; + } + return result; +} module.exports = tally; diff --git a/Sprint-2/implement/tally.test.js b/Sprint-2/implement/tally.test.js index 2ceffa8dd..68e1ec667 100644 --- a/Sprint-2/implement/tally.test.js +++ b/Sprint-2/implement/tally.test.js @@ -23,12 +23,60 @@ 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("tally on an array with duplicates returns correct counts for each unique item", () => { + expect( + tally([ + "CYF", + "CYF", + "AWS", + "Capgemini", + "Deloitte", + "Google", + "Slack", + "Capgemini", + ]) + ).toEqual({ CYF: 2, AWS: 1, Capgemini: 2, Deloitte: 1, Google: 1, Slack: 1 }); +}); + +test("tally on an array with duplicates returns correct counts for each unique item", () => { + expect(tally(["toString", "toString"])).toEqual({ toString: 2 }); +}); + +// Given an input that you mentioned in the review. +//test("tally on an array with duplicates returns correct counts for each unique item", () => { +// expect(tally(["toString", "toString"])).toEqual({ toString: 2 }); +//}); // Given an invalid input like a string // When passed to tally // Then it should throw an error +test("given invalid input throws an error", () => { + expect(() => tally("invalid")).toThrow("invalid input"); +}); + +// Given an invalid input like a number +test("given invalid input throws an error", () => { + expect(() => tally(123)).toThrow("invalid input"); +}); + +// Given an invalid input like an object +test("given invalid input throws an error", () => { + expect(() => tally({})).toThrow("invalid input"); +}); + +// Given an invalid input like null +test("given invalid input throws an error", () => { + expect(() => tally(null)).toThrow("invalid input"); +}); + +// Given an invalid input like undefined +test("given invalid input throws an error", () => { + expect(() => tally(undefined)).toThrow("invalid input"); +}); diff --git a/Sprint-2/interpret/invert.js b/Sprint-2/interpret/invert.js index bb353fb1f..e8bd4fb20 100644 --- a/Sprint-2/interpret/invert.js +++ b/Sprint-2/interpret/invert.js @@ -8,22 +8,32 @@ function invert(obj) { const invertedObj = {}; - + if (typeof obj !== "object" || obj === null || Array.isArray(obj)) + return invertedObj; for (const [key, value] of Object.entries(obj)) { - invertedObj.key = value; + invertedObj[value] = key; } return invertedObj; } // a) What is the current return value when invert is called with { a : 1 } +// {key: 1} // b) What is the current return value when invert is called with { a: 1, b: 2 } +// {key: 2} // c) What is the target return value when invert is called with {a : 1, b: 2} +// {"1": "a", "2": "b"} // c) What does Object.entries return? Why is it needed in this program? +// it returns an array of the pairs properties [key, value] of the object. +// it is needed in this program to iterate over the key value pairs of the object to swap them in new object. // d) Explain why the current return value is different from the target output +// because first it doesn't swap. +// second, we're using the dot notation to assign the value where we should user the brackets notation as the key is variable and not a string literal. // e) Fix the implementation of invert (and write tests to prove it's fixed!) + +module.exports = invert; diff --git a/Sprint-2/interpret/invert.test.js b/Sprint-2/interpret/invert.test.js new file mode 100644 index 000000000..927bed307 --- /dev/null +++ b/Sprint-2/interpret/invert.test.js @@ -0,0 +1,36 @@ +const invert = require("./invert.js"); +// writing some tests. +// Given an simple ordinary object. +test("invert on a simple object returns the correct inverted object", () => { + expect(invert({ a: 1 })).toEqual({ 1: "a" }); +}); +// Given an object with multiple key value pairs. +test("invert on an object with multiple key value pairs returns the correct inverted object", () => { + expect(invert({ a: 1, b: 2 })).toEqual({ 1: "a", 2: "b" }); +}); +// Given an object with duplicate values. +test("invert on an object with duplicate values returns the correct inverted object", () => { + expect(invert({ a: 1, b: 1 })).toEqual({ 1: "a", 1: "b" }); +}); +// Given an empty object. +test("invert on an empty object returns an empty object", () => { + expect(invert({})).toEqual({}); +}); +// Given an invalid input like a string. +test("invert on an invalid input like a string returns an empty object", () => { + expect(invert("invalid")).toEqual({}); +}); + +// Given an invalid input like a number. +test("invert on an invalid input like a number returns an empty object", () => { + expect(invert(123)).toEqual({}); +}); +// Given an invalid input like an array. +test("invert on an invalid input like an array returns an empty object", () => { + expect(invert([1, 2, 3])).toEqual({}); +}); +// Given an invalid input like null or undefined. +test("invert on an invalid input like null or undefined returns an empty object", () => { + expect(invert(null)).toEqual({}); + expect(invert(undefined)).toEqual({}); +});