diff --git a/Sprint-2/debug/address.js b/Sprint-2/debug/address.js index 940a6af83..7ccdb6d26 100644 --- a/Sprint-2/debug/address.js +++ b/Sprint-2/debug/address.js @@ -1,7 +1,9 @@ // Predict and explain first... - +// I predict the code will print "My house number is defines means undefined. // This code should log out the houseNumber from the address object // but it isn't working... +// Explanation: address is an object, and address[0] looks for a property named "0", which does not exist. + // Fix anything that isn't working const address = { @@ -12,4 +14,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..aa0aead82 100644 --- a/Sprint-2/debug/author.js +++ b/Sprint-2/debug/author.js @@ -1,7 +1,9 @@ // Predict and explain first... +// The program will show an error because an object is not iterable. // 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 +// We cannot use a for loop directly to an object because objects are not iterable in JavaScript. const author = { firstName: "Zadie", @@ -11,6 +13,6 @@ const author = { alive: true, }; -for (const value of author) { +for (const value of Object.values(author)) { console.log(value); } diff --git a/Sprint-2/debug/recipe.js b/Sprint-2/debug/recipe.js index 6cbdd22cd..585875e9d 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -1,7 +1,10 @@ // Predict and explain first... +// The program will display the title and serving information, but it will not display the +// ingredients correctly because we are trying to print the whole object. // This program should log out the title, how many it serves and the ingredients. // Each ingredient should be logged on a new line +// ingredients:${recipe} converts the whole object into a string rather than showing individual ingredients. // How can you fix it? const recipe = { @@ -12,4 +15,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..13fcc66a9 100644 --- a/Sprint-2/implement/contains.js +++ b/Sprint-2/implement/contains.js @@ -1,3 +1,8 @@ -function contains() {} +function contains(object, property) { + if (object === null || typeof object !== "object" || Array.isArray(object)) { + throw new Error("contains requires an object"); + } + return Object.hasOwn(object, property); +} module.exports = contains; diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index 719272787..c0b6104cc 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -24,17 +24,41 @@ as an array isn't an 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({}, "name")).toBe(false); +}); // Given an object with properties // When passed to contains with an existing property name // Then it should return true - +test("contains on an object with an existing property return ture", () => { + expect(contains({ name: "Cat", age: 10 }, "name")).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 an object with a non-existent property return false", () => { + expect(contains({ name: "Ramesh", age: 23 }, "location")).toBe(false); +}); // Given a value that isn't an object - an array, a string, a number, // null, or no argument at all // When passed to contains // Then it should throw Error("contains requires an object") // (careful: typeof [] and typeof null are both "object") +test("contains throws an error for a string", () => { + expect(() => contains("India", "0")).toThrow("contains requires an object"); +}); +test("contains throws an error for an array", () => { + expect(() => contains([1, 2, 3], "0")).toThrow("contains requires an object"); +}); +test("contains throws an error for null", () => { + expect(() => contains(null, "name")).toThrow("contains requires an object"); +}); + +test("contains throws an error when no argument is given", () => { + expect(() => contains()).toThrow("contains requires an object"); +}); +test("contains throws an error for a number", () => { + expect(() => contains(100, "name")).toThrow("contains requires an object"); +}); diff --git a/Sprint-2/implement/lookup.js b/Sprint-2/implement/lookup.js index a6746e07f..a859c918b 100644 --- a/Sprint-2/implement/lookup.js +++ b/Sprint-2/implement/lookup.js @@ -1,5 +1,15 @@ -function createLookup() { +function createLookup(pairs) { // implementation here + const lookup = {}; + + for (const pair of pairs) { + 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..f1de08c95 100644 --- a/Sprint-2/implement/lookup.test.js +++ b/Sprint-2/implement/lookup.test.js @@ -33,3 +33,12 @@ It should return: 'CA': 'CAD' } */ +test("creates a country currency code lookup for multiple codes", () => { + const pairs = [ + ["US", "USD"], + ["CA", "CAD"], + ]; + const result = createLookup(pairs); + + expect(result).toEqual({ US: "USD", CA: "CAD" }); +}); diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 45ec4e5f3..f0ff3035d 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -1,13 +1,32 @@ function parseQueryString(queryString) { const queryParams = {}; + 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; + } + + const [key, ...rest] = pair.split("="); + const value = rest.join("="); + + const decodedKey = decodeURIComponent(key.replace(/\+/g, " ")); + const decodedValue = decodeURIComponent(value.replace(/\+/g, " ")); + + if (decodedKey in queryParams) { + if (Array.isArray(queryParams[decodedKey])) { + queryParams[decodedKey].push(decodedValue); + } else { + queryParams[decodedKey] = [queryParams[decodedKey], decodedValue]; + } + } else { + queryParams[decodedKey] = decodedValue; + } } return queryParams; diff --git a/Sprint-2/implement/querystring.test.js b/Sprint-2/implement/querystring.test.js index 328b8df61..e9e0e9abd 100644 --- a/Sprint-2/implement/querystring.test.js +++ b/Sprint-2/implement/querystring.test.js @@ -3,7 +3,7 @@ // Below are some test cases the implementation doesn't handle well. // Fix the implementation for these tests, 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"); test("should parse values containing '='", () => { expect(parseQueryString("equation=a=b-2")).toEqual({ diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index f47321812..7fa4f1a06 100644 --- a/Sprint-2/implement/tally.js +++ b/Sprint-2/implement/tally.js @@ -1,3 +1,26 @@ -function tally() {} +function tally(items) { + if (!Array.isArray(items)) { + throw new Error("tally requires an array"); + } + + const counts = {}; + + for (const item of items) { + const key = String(item); + + if (Object.prototype.hasOwnProperty.call(counts, key)) { + counts[key] += 1; + } else { + Object.defineProperty(counts, key, { + value: 1, + writable: true, + enumerable: true, + configurable: true, + }); + } + } + + return counts; +} module.exports = tally; diff --git a/Sprint-2/implement/tally.test.js b/Sprint-2/implement/tally.test.js index ca763c296..bee676075 100644 --- a/Sprint-2/implement/tally.test.js +++ b/Sprint-2/implement/tally.test.js @@ -14,21 +14,80 @@ const tally = require("./tally.js"); * tally(['a', 'a', 'b', 'c']), target output: { a : 2, b: 1, c: 1 } */ -// Acceptance criteria: - -// Given a function called tally -// When passed an array of items +// Given an array of items +// When passed to tally // Then it should return an object containing the count for each unique item +test("tally on a single item counts it once", () => { + expect(tally(["a"])).toEqual({ a: 1 }); +}); + +test("tally on repeated items counts each one", () => { + expect(tally(["a", "a", "a"])).toEqual({ a: 3 }); +}); + +test("tally on a mix of items counts each unique item", () => { + expect(tally(["a", "a", "b", "c"])).toEqual({ a: 2, b: 1, c: 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 counts duplicates that are not next to each other", () => { + expect(tally(["a", "b", "a", "c", "b", "a"])).toEqual({ a: 3, b: 2, c: 1 }); +}); // Given an invalid input like a string, a number, or no argument at all // When passed to tally // Then it should throw Error("tally requires an array") +test("tally throws on a string", () => { + expect(() => tally("abc")).toThrow(new Error("tally requires an array")); +}); + +test("tally throws on a number", () => { + expect(() => tally(42)).toThrow(new Error("tally requires an array")); +}); + +test("tally throws when called with no argument", () => { + expect(() => tally()).toThrow(new Error("tally requires an array")); +}); + +test("tally throws on null, undefined and plain objects", () => { + expect(() => tally(null)).toThrow(new Error("tally requires an array")); + expect(() => tally(undefined)).toThrow(new Error("tally requires an array")); + expect(() => tally({ a: 1 })).toThrow(new Error("tally requires an array")); +}); + +// Extra edge cases +test("tally handles item names that match built-in object properties", () => { + const result = tally(["constructor", "toString", "constructor"]); + expect(result.constructor).toBe(2); + expect(result.toString).toBe(1); +}); + +test("tally does not allow prototype pollution via __proto__", () => { + const result = tally(["__proto__", "__proto__"]); + expect({}.polluted).toBeUndefined(); + expect(Object.keys(result)).toEqual(["__proto__"]); + expect(Object.getOwnPropertyDescriptor(result, "__proto__").value).toBe(2); +}); + +test("tally counts numbers and booleans by their string form", () => { + expect(tally([1, 1, 2, true, true])).toEqual({ 1: 2, 2: 1, true: 2 }); +}); + +test("tally treats different cases as different items", () => { + expect(tally(["a", "A", "a"])).toEqual({ a: 2, A: 1 }); +}); + +test("tally does not change the array it is given", () => { + const input = ["a", "b", "a"]; + tally(input); + expect(input).toEqual(["a", "b", "a"]); +}); diff --git a/Sprint-2/interpret/invert.js b/Sprint-2/interpret/invert.js index bb353fb1f..3dbe39a80 100644 --- a/Sprint-2/interpret/invert.js +++ b/Sprint-2/interpret/invert.js @@ -10,20 +10,26 @@ function invert(obj) { const invertedObj = {}; for (const [key, value] of Object.entries(obj)) { - invertedObj.key = value; + // it's the error + // invertedObj.key = value; + // fixed the code + invertedObj[value] = key; } return invertedObj; } +module.exports = invert; // a) What is the current return value when invert is called with { a : 1 } - +// ==>The return value when invert is called is "a":"1" // b) What is the current return value when invert is called with { a: 1, b: 2 } - +// ==>The return value when invert is called are "a":"1", "b":"2" // c) What is the target return value when invert is called with {a : 1, b: 2} - +// ==>The target return value when invert is called with{a:1, b: 2} are "1": "a", "2": "b" // c) What does Object.entries return? Why is it needed in this program? - +// ==>It turns an object into an array of [key, value] pairs. Object.entries({ a: 1, b: 2 }) gives [["a", 1], ["b", 2]]. It’s needed because a for...of loop can’t go through a plain object directly, and the function needs both the key and the value of each property. Object.entries provides both, and const [key, value] unpacks each pair. // d) Explain why the current return value is different from the target output - +// ==>first it used the dot notation instead of square bracket which is accessing the property using a literal name +// ==>second it is not inverted it still uses key value instead of value key so that it returns the inverted one. // e) Fix the implementation of invert (and write tests to prove it's fixed!) +// ==>i have fixed the code and for testing i made the another file and make the edges cases. diff --git a/Sprint-2/interpret/invert.test.js b/Sprint-2/interpret/invert.test.js new file mode 100644 index 000000000..fbe32fdc5 --- /dev/null +++ b/Sprint-2/interpret/invert.test.js @@ -0,0 +1,43 @@ +const invert = require("./invert.js"); + +test("swaps keys and values for the example in the exercise", () => { + expect(invert({ x: 10, y: 20 })).toEqual({ 10: "x", 20: "y" }); +}); + +test("works with a single key", () => { + expect(invert({ a: 1 })).toEqual({ 1: "a" }); +}); + +test("works with two keys", () => { + expect(invert({ a: 1, b: 2 })).toEqual({ 1: "a", 2: "b" }); +}); + +test("returns an empty object for an empty object", () => { + expect(invert({})).toEqual({}); +}); + +test("string values become keys", () => { + expect(invert({ first: "one", second: "two" })).toEqual({ + one: "first", + two: "second", + }); +}); + +test("original numeric keys come back as strings", () => { + expect(invert({ 1: "a", 2: "b" })).toEqual({ a: "1", b: "2" }); +}); + +test("when two keys share a value, the last key wins", () => { + expect(invert({ a: 1, b: 1 })).toEqual({ 1: "b" }); +}); + +test("does not change the original object", () => { + const original = { a: 1, b: 2 }; + invert(original); + expect(original).toEqual({ a: 1, b: 2 }); +}); + +test("inverting twice gives back the original when values are unique strings", () => { + const original = { a: "x", b: "y" }; + expect(invert(invert(original))).toEqual(original); +});