From 28540289c9a86a1dbd3727be350081b9618c1296 Mon Sep 17 00:00:00 2001 From: Ammad Date: Mon, 10 Nov 2025 17:11:39 +0000 Subject: [PATCH 01/10] Completed address.js --- Sprint-2/debug/address.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Sprint-2/debug/address.js b/Sprint-2/debug/address.js index 940a6af83..a8fc2386c 100644 --- a/Sprint-2/debug/address.js +++ b/Sprint-2/debug/address.js @@ -1,5 +1,8 @@ // Predict and explain first... +// I predict that the console will log "My house number is undefined" +// because it is using array index notation whereas address is an object. + // This code should log out the houseNumber from the address object // but it isn't working... // Fix anything that isn't working @@ -12,4 +15,4 @@ const address = { postcode: "XYZ 123", }; -console.log(`My house number is ${address[0]}`); +console.log(`My house number is ${address["houseNumber"]}`); From dd03ab32c9cd198c865acfa099a1a1d392e190d1 Mon Sep 17 00:00:00 2001 From: Ammad Date: Mon, 10 Nov 2025 17:22:05 +0000 Subject: [PATCH 02/10] Completed author.js --- Sprint-2/debug/author.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Sprint-2/debug/author.js b/Sprint-2/debug/author.js index 8c2125977..8f925b6bb 100644 --- a/Sprint-2/debug/author.js +++ b/Sprint-2/debug/author.js @@ -1,8 +1,14 @@ // Predict and explain first... +// I predict that the console will log "undefined" because +// it is looping through elements in a collection (e.g. arrays and sets) +// whereas author is an object. + // 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 +// The loop is trying to loop through an array + const author = { firstName: "Zadie", lastName: "Smith", @@ -11,6 +17,6 @@ const author = { alive: true, }; -for (const value of author) { +for (value in author) { console.log(value); } From 00093ebd287b6c5e3fead25ed7763d89e2a420cc Mon Sep 17 00:00:00 2001 From: Ammad Date: Tue, 11 Nov 2025 19:36:34 +0000 Subject: [PATCH 03/10] Completed recipe.js --- Sprint-2/debug/recipe.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Sprint-2/debug/recipe.js b/Sprint-2/debug/recipe.js index 6cbdd22cd..0301114f0 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -1,5 +1,7 @@ // Predict and explain first... +// The console will print the recipe title, serves and the recipe object itself rather than the actual ingredient + // 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? @@ -10,6 +12,8 @@ const recipe = { ingredients: ["olive oil", "tomatoes", "salt", "pepper"], }; -console.log(`${recipe.title} serves ${recipe.serves} - ingredients: -${recipe}`); +console.log(`titie: ${recipe.title}, serves: ${recipe.serves}, ingredients:\n`); + +for (const ingredient of recipe.ingredients) { + console.log(ingredient); +} From 1e3daf27bc466e0472b0181d9659bb2aa32191ad Mon Sep 17 00:00:00 2001 From: Ammad Date: Tue, 11 Nov 2025 20:56:16 +0000 Subject: [PATCH 04/10] Completed contains --- Sprint-2/implement/contains.js | 8 +++++++- Sprint-2/implement/contains.test.js | 26 +++++++++++++++++++++++++- package.json | 8 ++++++++ 3 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 package.json diff --git a/Sprint-2/implement/contains.js b/Sprint-2/implement/contains.js index cd779308a..d1a5a25f5 100644 --- a/Sprint-2/implement/contains.js +++ b/Sprint-2/implement/contains.js @@ -1,3 +1,9 @@ -function contains() {} +function contains(object, property) { + if (object === null || typeof object !== "object" || Array.isArray(object)) { + throw new Error("object is not an object type!"); + } + + return Object.prototype.hasOwnProperty.call(object, property); +} module.exports = contains; diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index 326bdb1f2..bb7efbcf1 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -16,20 +16,44 @@ 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("object contains the property returns true", () => { + const object = { forename: "John" }; + expect(contains(object, "forename")).toEqual(true); + expect(contains(object, "surname")).toEqual(false); +}); // Given an empty object // When passed to contains // Then it should return false -test.todo("contains on empty object returns false"); +test("empty object returns false", () => { + const object = {}; + const result = contains(object); + expect(result).toEqual(false); +}); // Given an object with properties // When passed to contains with an existing property name // Then it should return true +test("object contains properties returns true for a property", () => { + const object = { forename: "John", surname: "Appleseed" }; + const property = "surname"; + const result = contains(object, property); + expect(result).toEqual(true); +}); // Given an object with properties // When passed to contains with a non-existent property name // Then it should return false +test("object returns false for a non-existent property", () => { + const object = { forename: "John", surname: "Appleseed" }; + const property = "middlename"; + const result = contains(object, property); + expect(result).toEqual(false); +}); // Given invalid parameters like an array // When passed to contains // Then it should return false or throw an error +test("object throws an error for a non-object type", () => { + expect(() => contains([])).toThrow("object is not an object type!"); +}); diff --git a/package.json b/package.json new file mode 100644 index 000000000..37e34c85a --- /dev/null +++ b/package.json @@ -0,0 +1,8 @@ +{ + "scripts": { + "test": "jest" + }, + "devDependencies": { + "jest": "^30.2.0" + } +} From 4c5b4703e1295785d1120d77ed6813f19e9d632e Mon Sep 17 00:00:00 2001 From: Ammad Date: Tue, 11 Nov 2025 22:16:54 +0000 Subject: [PATCH 05/10] Completed lookup --- Sprint-2/implement/lookup.js | 4 ++-- Sprint-2/implement/lookup.test.js | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/Sprint-2/implement/lookup.js b/Sprint-2/implement/lookup.js index a6746e07f..dcbb17109 100644 --- a/Sprint-2/implement/lookup.js +++ b/Sprint-2/implement/lookup.js @@ -1,5 +1,5 @@ -function createLookup() { - // implementation here +function createLookup(arrayPairs) { + return Object.fromEntries(arrayPairs); } module.exports = createLookup; diff --git a/Sprint-2/implement/lookup.test.js b/Sprint-2/implement/lookup.test.js index 547e06c5a..5974fcdfd 100644 --- a/Sprint-2/implement/lookup.test.js +++ b/Sprint-2/implement/lookup.test.js @@ -1,6 +1,20 @@ 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", () => { + const arrayPairs = [ + ["US", "USD"], + ["CA", "CAD"], + ]; + + const object = { + US: "USD", + CA: "CAD", + }; + + const result = createLookup(arrayPairs); + + expect(result).toEqual(object); +}); /* From f47f94d333905c0b2331bf3877a2c8673918743b Mon Sep 17 00:00:00 2001 From: Ammad Date: Wed, 12 Nov 2025 13:35:30 +0000 Subject: [PATCH 06/10] Completed tally --- Sprint-2/implement/tally.js | 10 +++++++++- Sprint-2/implement/tally.test.js | 21 ++++++++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index f47321812..430447f2d 100644 --- a/Sprint-2/implement/tally.js +++ b/Sprint-2/implement/tally.js @@ -1,3 +1,11 @@ -function tally() {} +function tally(array) { + if (!Array.isArray(array)) throw new Error("array is not an array!"); + + return array.reduce((accumulator, currentValue) => { + const value = accumulator[currentValue]; + accumulator[currentValue] = value ? value + 1 : 1; + return accumulator; + }, {}); +} module.exports = tally; diff --git a/Sprint-2/implement/tally.test.js b/Sprint-2/implement/tally.test.js index 2ceffa8dd..733a0d669 100644 --- a/Sprint-2/implement/tally.test.js +++ b/Sprint-2/implement/tally.test.js @@ -19,16 +19,35 @@ 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("output containing the count for each unique item", () => { + const array = ["a", "b", "c"]; + const result = tally(array); + const output = { a: 1, b: 1, c: 1 }; + expect(result).toEqual(output); +}); // 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", () => { + const array = []; + const result = tally(array); + expect(result).toEqual({}); +}); // Given an array with duplicate items // When passed to tally // Then it should return counts for each unique item +test("array with duplicate items", () => { + const array = ["a", "b", "b", "c", "c", "c"]; + const result = tally(array); + const output = { a: 1, b: 2, c: 3 }; + expect(result).toEqual(output); +}); // Given an invalid input like a string // When passed to tally // Then it should throw an error +test("invalid input", () => { + expect(() => tally("Hello, World!")).toThrow("array is not an array!"); +}); From fbbf17a157b535bfc996b2a639d73157226a4948 Mon Sep 17 00:00:00 2001 From: Ammad Date: Wed, 12 Nov 2025 14:15:15 +0000 Subject: [PATCH 07/10] Refactor function parseQueryString() --- Sprint-2/implement/querystring.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 45ec4e5f3..e1fca45bf 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -1,12 +1,11 @@ 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("="); + if (!queryParams) return queryParams; + + const searchParameters = new URLSearchParams(queryString); + + for (const [key, value] of searchParameters.entries()) { queryParams[key] = value; } From 386ece3d5bf966ffba0b3fc292eb19cdd3101179 Mon Sep 17 00:00:00 2001 From: Ammad Date: Wed, 12 Nov 2025 14:36:15 +0000 Subject: [PATCH 08/10] Write tests in querystring.test --- Sprint-2/implement/querystring.test.js | 100 +++++++++++++++++++++++-- 1 file changed, 95 insertions(+), 5 deletions(-) diff --git a/Sprint-2/implement/querystring.test.js b/Sprint-2/implement/querystring.test.js index 3e218b789..25e602926 100644 --- a/Sprint-2/implement/querystring.test.js +++ b/Sprint-2/implement/querystring.test.js @@ -3,10 +3,100 @@ // 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"); -test("parses querystring values containing =", () => { - expect(parseQueryString("equation=x=y+1")).toEqual({ - "equation": "x=y+1", - }); +// Verifies that raw '+' characters are interpreted as spaces by URLSearchParams +test("parses query string values with unencoded '+' as spaces", () => { + const result = parseQueryString("equation=x=y+1"); + const output = { equation: "x=y 1" }; // because URLSearchParams decodes '+' as a space according to URL encoding rules + expect(result).toEqual(output); +}); + +// Verifies that properly URL-encoded '=' and '+' characters are preserved correctly +test("parses query string values with encoded '=' and '+' correctly", () => { + const result = parseQueryString("equation=x%3Dy%2B1"); // '%3D' => '=', '%2B' => '+' + const output = { equation: "x=y+1" }; // because URLSearchParams decodes percent-encoded characters but preserves literal '+' when encoded as '%2B' + expect(result).toEqual(output); +}); + +test("returns empty object for empty string", () => { + const result = parseQueryString(); + const output = {}; + expect(result).toEqual(output); +}); + +test("handles undefined input", () => { + const result = parseQueryString(undefined); + const output = {}; + expect(result).toEqual(output); +}); + +test("handles null input", () => { + const result = parseQueryString(null); + const output = {}; + expect(result).toEqual(output); +}); + +test("handles key without value", () => { + const result = parseQueryString("name="); + const output = { name: "" }; + expect(result).toEqual(output); +}); + +test("handles missing key", () => { + const result = parseQueryString("=John"); + const output = { "": "John" }; + expect(result).toEqual(output); +}); + +test("handles multiple key-value pairs", () => { + const result = parseQueryString("name=John&age=30&city=London"); + const output = { + name: "John", + age: "30", + city: "London", + }; + expect(result).toEqual(output); +}); + +test("handles duplicate keys by overwriting previous value", () => { + const result = parseQueryString("tag=js&tag=web"); + const output = { tag: "web" }; + expect(result).toEqual(output); +}); + +test("handles URL-encoded characters", () => { + const result = parseQueryString("city=New%20York"); + const output = { city: "New York" }; + expect(result).toEqual(output); +}); + +test("handles unencoded special characters", () => { + const result = parseQueryString("greeting=hello world"); + const output = { greeting: "hello world" }; + expect(result).toEqual(output); +}); + +test("handles keys containing '='", () => { + const result = parseQueryString("a=b=c=d"); + const output = { a: "b=c=d" }; + expect(result).toEqual(output); +}); + +test("ignores leading and trailing ampersands", () => { + const result = parseQueryString("&name=John&age=30&"); + const output = { name: "John", age: "30" }; + expect(result).toEqual(output); +}); + +test("ignores empty pairs", () => { + const result = parseQueryString("name=John&&age=30&"); + const output = { name: "John", age: "30" }; + expect(result).toEqual(output); +}); + +test("handles equals sign only", () => { + const result = parseQueryString("="); + const output = { "": "" }; + expect(result).toEqual(output); }); From ea28e69a697f2e888424f2ebc015804a7b985223 Mon Sep 17 00:00:00 2001 From: Ammad Date: Wed, 12 Nov 2025 16:59:16 +0000 Subject: [PATCH 09/10] Completed invert, refactored function invert() and created invert.test.js file to write tests. --- Sprint-2/interpret/invert.js | 23 ++++++++++++++- Sprint-2/interpret/invert.test.js | 48 +++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) 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..052560785 100644 --- a/Sprint-2/interpret/invert.js +++ b/Sprint-2/interpret/invert.js @@ -7,23 +7,44 @@ // E.g. invert({x : 10, y : 20}), target output: {"10": "x", "20": "y"} function invert(obj) { + if (obj === null || typeof obj !== "object") { + throw new Error("Parameter `obj` must be an object type"); + } + const invertedObj = {}; for (const [key, value] of Object.entries(obj)) { - invertedObj.key = value; + invertedObj[value] = key; } return invertedObj; } +module.exports = invert; + // 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? +// Returns an array of key/values of the enumerable own properties of an object +// It is a convenient way to map the object properties and values into key-value pairs + // d) Explain why the current return value is different from the target output +// Because of this line: +// invertedObj.key = value; +// The property "key" is declared and the value is assigned to the "key" property + // e) Fix the implementation of invert (and write tests to prove it's fixed!) + +// Refer to invert.test.js diff --git a/Sprint-2/interpret/invert.test.js b/Sprint-2/interpret/invert.test.js new file mode 100644 index 000000000..f35fc3138 --- /dev/null +++ b/Sprint-2/interpret/invert.test.js @@ -0,0 +1,48 @@ +const invert = require("./invert.js"); + +describe("invert()", () => { + test("inverts an object with a single property", () => { + const object = { a: 1 }; + const result = invert(object); + const output = { 1: "a" }; + expect(result).toEqual(output); + }); + + test("inverts an object with multiple properties", () => { + const object = { a: 1, b: 2, c: 3 }; + const result = invert(object); + const output = { 1: "a", 2: "b", 3: "c" }; + expect(result).toEqual(output); + }); + + test("handles duplicate values (last key wins)", () => { + const object = { a: 1, b: 1, c: 2 }; + const result = invert(object); + const output = { 1: "b", 2: "c" }; + expect(result).toEqual(output); + }); + + test("handles non-string values as keys", () => { + const object = { a: true, b: null, c: undefined }; + const result = invert(object); + const output = { true: "a", null: "b", undefined: "c" }; + expect(result).toEqual(output); + }); + + test("handles empty object", () => { + const object = {}; + const result = invert(object); + const output = {}; + expect(result).toEqual(output); + }); + + test("throws error for non-object input", () => { + expect(() => invert(null)).toThrow( + "Parameter `obj` must be an object type" + ); + expect(() => invert(42)).toThrow("Parameter `obj` must be an object type"); + expect(() => invert("string")).toThrow( + "Parameter `obj` must be an object type" + ); + }); +}); From 6ae2abb03a6477c9d49ea1f7b6c60d576976f3cc Mon Sep 17 00:00:00 2001 From: Ammad Date: Fri, 21 Nov 2025 17:31:42 +0000 Subject: [PATCH 10/10] Address the issues from the pull request comments --- Sprint-2/debug/recipe.js | 10 +++++----- Sprint-2/implement/tally.js | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Sprint-2/debug/recipe.js b/Sprint-2/debug/recipe.js index 0301114f0..e82edad0f 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -12,8 +12,8 @@ const recipe = { ingredients: ["olive oil", "tomatoes", "salt", "pepper"], }; -console.log(`titie: ${recipe.title}, serves: ${recipe.serves}, ingredients:\n`); - -for (const ingredient of recipe.ingredients) { - console.log(ingredient); -} +console.log( + `titie: ${recipe.title}, serves: ${ + recipe.serves + }, ingredients: ${recipe.ingredients.join(", ")}` +); diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index 430447f2d..02b621fd4 100644 --- a/Sprint-2/implement/tally.js +++ b/Sprint-2/implement/tally.js @@ -5,7 +5,7 @@ function tally(array) { const value = accumulator[currentValue]; accumulator[currentValue] = value ? value + 1 : 1; return accumulator; - }, {}); + }, Object.create(null)); } module.exports = tally;