From 62fe3e1b1e83bbe07ee8aecdeb4cd3c2ab903973 Mon Sep 17 00:00:00 2001 From: TTiamiyu Date: Mon, 27 Jul 2026 22:57:31 +0100 Subject: [PATCH 01/13] Fix address logging to correctly display house number --- Sprint-2/debug/address.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sprint-2/debug/address.js b/Sprint-2/debug/address.js index 940a6af83..c83d6c93d 100644 --- a/Sprint-2/debug/address.js +++ b/Sprint-2/debug/address.js @@ -1,5 +1,7 @@ // Predict and explain first... +/* Prediction - the house number would return as undefined */ + // This code should log out the houseNumber from the address object // but it isn't working... // Fix anything that isn't working @@ -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}`); From f92f6221939cda4b2ec5a702a0e14a9de94236d0 Mon Sep 17 00:00:00 2001 From: TTiamiyu Date: Mon, 27 Jul 2026 23:03:39 +0100 Subject: [PATCH 02/13] Clarify prediction explanation in address.js comments --- 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 c83d6c93d..54d569aae 100644 --- a/Sprint-2/debug/address.js +++ b/Sprint-2/debug/address.js @@ -1,6 +1,9 @@ // Predict and explain first... -/* Prediction - the house number would return as undefined */ +/* Prediction - the house number would return as undefined. + +Explaination - "address" is a plain javascript object, not an array, arrays make use of the numeric index positions like it was used in +the initial code but objects use key value pairs (properties) */ // This code should log out the houseNumber from the address object // but it isn't working... From b6eb718b95dff3358d675695f52844148f7a48f3 Mon Sep 17 00:00:00 2001 From: TTiamiyu Date: Mon, 27 Jul 2026 23:17:10 +0100 Subject: [PATCH 03/13] Fix TypeError in author.js by using Object.values() to iterate over object properties --- 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..cd324f5ff 100644 --- a/Sprint-2/debug/author.js +++ b/Sprint-2/debug/author.js @@ -1,5 +1,11 @@ // Predict and explain first... +/* Prediction - TypeError: author is not iterable. + +Explaination - The "for... of" loop is designed fir iterable objects like arrays, strings, maps or sets, +which have built in protocols. plain javascript objects like "author" are not iterable by default, +which would cause the error when the "for... of" loop is used. */ + // 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 +17,6 @@ const author = { alive: true, }; -for (const value of author) { +for (const value of Object.Values(author)) { console.log(value); } From 391bd80c4d2aba1afac0cdc020e7355b6691f368 Mon Sep 17 00:00:00 2001 From: TTiamiyu Date: Mon, 27 Jul 2026 23:29:11 +0100 Subject: [PATCH 04/13] Fix recipe logging to display ingredients correctly --- Sprint-2/debug/recipe.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Sprint-2/debug/recipe.js b/Sprint-2/debug/recipe.js index 6cbdd22cd..eec705430 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -1,5 +1,11 @@ // Predict and explain first... +/* PREDICTION - it would print out the name, how many it serves perfectly but wouldn't work well for the ingredients. + +EXPLANATION +Printing the whole object instead of ingredients: The template literal uses ${recipe} at the end. When JavaScript tries to insert an object into a template literal string, +it calls .toString() on the object, which results in the generic string "[object Object]" instead of showing its properties or the array inside. */ + // 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 +16,5 @@ const recipe = { ingredients: ["olive oil", "tomatoes", "salt", "pepper"], }; -console.log(`${recipe.title} serves ${recipe.serves} - ingredients: -${recipe}`); +console.log(`${recipe.title} serves ${recipe.serves}\n +ingredients:${recipe.ingredients.join("\n")}`); From dd5a43cd9595a6b5a55bdce98ce7e5cab2828de0 Mon Sep 17 00:00:00 2001 From: TTiamiyu Date: Mon, 27 Jul 2026 23:34:19 +0100 Subject: [PATCH 05/13] Refactor contains tests to include cases for empty objects and invalid parameters --- Sprint-2/implement/contains.test.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index 326bdb1f2..185e1c542 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -20,16 +20,34 @@ 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"); + +describe("contains()", () => { + test("returns false when given an empty object", () => { + 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 when the object contains the property name", () => { + 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("returns false when the object does not contain the property name", () => { + 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("returns false when passed invalid parameters like an array or primitive", () => { + expect(contains(["a", "b"], "0")).toBe(false); + expect(contains(null, "a")).toBe(false); + expect(contains("string", "length")).toBe(false); + }); +}); From c7406b2bd48d4b0e5f6c5c3a4a47457bc02f7f32 Mon Sep 17 00:00:00 2001 From: TTiamiyu Date: Mon, 27 Jul 2026 23:37:51 +0100 Subject: [PATCH 06/13] Implement contains function to check for object properties --- Sprint-2/implement/contains.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Sprint-2/implement/contains.js b/Sprint-2/implement/contains.js index cd779308a..e5e13ed89 100644 --- a/Sprint-2/implement/contains.js +++ b/Sprint-2/implement/contains.js @@ -1,3 +1,8 @@ -function contains() {} +function contains() { + if (typeof obj !== "object" || obj === null || Array.isArray(obj)) { + return false; + } + return Object.prototype.hasOwnProperty.call(obj, prop); +} module.exports = contains; From 1014ae015b92c5ee8ece3dc087e3578e20bb0491 Mon Sep 17 00:00:00 2001 From: TTiamiyu Date: Mon, 27 Jul 2026 23:41:05 +0100 Subject: [PATCH 07/13] Add tests for createLookup function to validate country-currency mapping and handle edge cases --- Sprint-2/implement/lookup.test.js | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/Sprint-2/implement/lookup.test.js b/Sprint-2/implement/lookup.test.js index 547e06c5a..ef03e3d8c 100644 --- a/Sprint-2/implement/lookup.test.js +++ b/Sprint-2/implement/lookup.test.js @@ -1,6 +1,30 @@ const createLookup = require("./lookup.js"); -test.todo("creates a country currency code lookup for multiple codes"); +describe("createLookup()", () => { + test("creates a country currency code lookup for multiple codes", () => { + const input = [ + ["US", "USD"], + ["CA", "CAD"], + ["GB", "GBP"], + ]; + const expected = { + US: "USD", + CA: "CAD", + GB: "GBP", + }; + + expect(createLookup(input)).toEqual(expected); + }); + + test("returns an empty object when passed an empty array", () => { + expect(createLookup([])).toEqual({}); + }); + + test("returns an empty object when passed invalid inputs", () => { + expect(createLookup(null)).toEqual({}); + expect(createLookup("invalid")).toEqual({}); + }); +}); /* From 655bb314347fa77d3c60796529a47dc88465fbc2 Mon Sep 17 00:00:00 2001 From: TTiamiyu Date: Mon, 27 Jul 2026 23:43:01 +0100 Subject: [PATCH 08/13] Implement createLookup function to validate input and return an object from country-currency pairs --- Sprint-2/implement/lookup.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Sprint-2/implement/lookup.js b/Sprint-2/implement/lookup.js index a6746e07f..817a4a8dd 100644 --- a/Sprint-2/implement/lookup.js +++ b/Sprint-2/implement/lookup.js @@ -1,5 +1,8 @@ function createLookup() { - // implementation here + if (!Array.isArray(countryCurrencyPairs)) { + return {}; + } + return Object.fromEntries(countryCurrencyPairs); } module.exports = createLookup; From 63973cdaafe58bc01e5ce94d228b71a7c990856e Mon Sep 17 00:00:00 2001 From: TTiamiyu Date: Mon, 27 Jul 2026 23:49:47 +0100 Subject: [PATCH 09/13] Refactor parseQueryString function to handle empty inputs, decode parameters, and support duplicate keys; add corresponding tests for edge cases --- Sprint-2/implement/querystring.js | 65 ++++++++++++++++++++++---- Sprint-2/implement/querystring.test.js | 20 +++++++- 2 files changed, 76 insertions(+), 9 deletions(-) diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 45ec4e5f3..84df4307f 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -1,16 +1,65 @@ function parseQueryString(queryString) { - const queryParams = {}; - if (queryString.length === 0) { - return queryParams; + const result = {}; + + // Return empty object for empty or non-string inputs + if (typeof queryString !== "string" || !queryString.trim()) { + return result; } - const keyValuePairs = queryString.split("&"); - for (const pair of keyValuePairs) { - const [key, value] = pair.split("="); - queryParams[key] = value; + // Optional: strip leading '?' if passed a full URL search string (e.g., "?a=1") + const sanitizedInput = queryString.startsWith("?") + ? queryString.slice(1) + : queryString; + + // Split pairs by '&' + const pairs = sanitizedInput.split("&"); + + for (const pair of pairs) { + // Ignore empty pairs (e.g. from consecutive '&&' or trailing '&') + if (!pair) continue; + + // Find the FIRST '=' to separate key and value correctly + const equalIndex = pair.indexOf("="); + + let rawKey = ""; + let rawValue = ""; + + if (equalIndex === -1) { + // Key with no '=' (e.g., "key" -> key="key", value="") + rawKey = pair; + rawValue = ""; + } else { + // Split strictly on the first '=' so values containing '=' stay intact + rawKey = pair.slice(0, equalIndex); + rawValue = pair.slice(equalIndex + 1); + } + + // Helper to decode query string encoding (+ to space, percent-encoding) + const decodeParam = (str) => { + try { + return decodeURIComponent(str.replace(/\+/g, " ")); + } catch { + // Fallback if URI decoding fails on malformed input + return str.replace(/\+/g, " "); + } + }; + + const key = decodeParam(rawKey); + const value = decodeParam(rawValue); + + // Handle single key vs duplicate keys + if (Object.prototype.hasOwnProperty.call(result, key)) { + if (Array.isArray(result[key])) { + result[key].push(value); + } else { + result[key] = [result[key], value]; + } + } else { + result[key] = value; + } } - return queryParams; + return result; } module.exports = parseQueryString; diff --git a/Sprint-2/implement/querystring.test.js b/Sprint-2/implement/querystring.test.js index 328b8df61..efc221086 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({ @@ -46,3 +46,21 @@ test("should store values of a key in an array when the key has 2 or more values foo: "bar", }); }); +test("should strip leading '?' if present", () => { + expect(parseQueryString("?foo=bar&baz=qux")).toEqual({ + foo: "bar", + baz: "qux", + }); +}); + +test("should handle empty or invalid inputs gracefully", () => { + expect(parseQueryString("")).toEqual({}); + expect(parseQueryString(null)).toEqual({}); + expect(parseQueryString(undefined)).toEqual({}); +}); + +test("should gracefully handle malformed percent encoding", () => { + expect(parseQueryString("key=%E0%A4")).toEqual({ + key: "%E0%A4", + }); +}); From 1874dec0b035a639e9fe10566323a34799c62c62 Mon Sep 17 00:00:00 2001 From: TTiamiyu Date: Mon, 27 Jul 2026 23:59:07 +0100 Subject: [PATCH 10/13] Refactor tally tests to implement missing test case for empty array and organize test structure for clarity --- Sprint-2/implement/tally.test.js | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/Sprint-2/implement/tally.test.js b/Sprint-2/implement/tally.test.js index 2ceffa8dd..c096d3077 100644 --- a/Sprint-2/implement/tally.test.js +++ b/Sprint-2/implement/tally.test.js @@ -23,12 +23,29 @@ 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"); -// Given an array with duplicate items -// When passed to tally -// Then it should return counts for each unique item +describe("tally()", () => { + test("tally on an empty array returns an empty object", () => { + expect(tally([])).toEqual({}); + }); -// Given an invalid input like a string -// When passed to tally -// Then it should throw an error + // Given an array with duplicate items + // When passed to tally + // Then it should return counts for each unique item + + test("returns counts for single and duplicate items", () => { + expect(tally(["a"])).toEqual({ a: 1 }); + expect(tally(["a", "a", "a"])).toEqual({ a: 3 }); + 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 when passed invalid input like a string or null", () => { + expect(() => tally("a string")).toThrow(TypeError); + expect(() => tally(123)).toThrow(TypeError); + expect(() => tally(null)).toThrow(TypeError); + }); +}); From 1c17fbf2e381f800170aeb1f205b3bab783721d8 Mon Sep 17 00:00:00 2001 From: TTiamiyu Date: Mon, 27 Jul 2026 23:59:20 +0100 Subject: [PATCH 11/13] Implement tally function to count occurrences of items in an array and handle non-array inputs --- Sprint-2/implement/tally.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index f47321812..96b76f842 100644 --- a/Sprint-2/implement/tally.js +++ b/Sprint-2/implement/tally.js @@ -1,3 +1,12 @@ -function tally() {} +function tally() { + if (!Array.isArray(items)) { + throw new TypeError("Input must be an array"); + } + return items.reduce((acc, item) => { + // Increment count if key exists, otherwise initialize to 1 + acc[item] = (acc[item] || 0) + 1; + return acc; + }, {}); +} module.exports = tally; From 75ce6cb5e3d46627503e179a8f871dbedecc0435 Mon Sep 17 00:00:00 2001 From: TTiamiyu Date: Tue, 28 Jul 2026 00:21:17 +0100 Subject: [PATCH 12/13] Fix invert function to correctly swap keys and values, add input validation for non-object types --- Sprint-2/interpret/invert.js | 45 ++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/Sprint-2/interpret/invert.js b/Sprint-2/interpret/invert.js index bb353fb1f..f20fd5ba5 100644 --- a/Sprint-2/interpret/invert.js +++ b/Sprint-2/interpret/invert.js @@ -5,6 +5,7 @@ // Then it should swap the keys and values in the object // E.g. invert({x : 10, y : 20}), target output: {"10": "x", "20": "y"} +/* function invert(obj) { const invertedObj = {}; @@ -15,15 +16,51 @@ function invert(obj) { return invertedObj; } +*/ +/* a) What is the current return value when invert is called with { a : 1 } -// 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} -// c) What does Object.entries return? Why is it needed in this program? +{ "1": "a", "2": "b" } +// d) What does Object.entries return? Why is it needed in this program? -// d) Explain why the current return value is different from the target output +Object.entries(obj) returns an array of an object's own key-value pairs as [key, value] tuples (e.g., Object.entries({ a: 1, b: 2 }) returns [['a', 1], ['b', 2]]). -// e) Fix the implementation of invert (and write tests to prove it's fixed!) +It is needed here so we can loop over both the property name (key) and its corresponding value (value) at the same time using array destructuring (const [key, value]). + +// e) Explain why the current return value is different from the target output + +There are two critical issues in the current loop +1. Static property assignment instead of dynamic lookup; +The code writes invertedObj.key = value. Using dot notation literally creates/overwrites a single key named "key" on the object every single iteration. +To use the variable's value dynamically as the object key, you must use bracket notation: invertedObj[value]. + +2. Flipped key and value; +The target output asks to swap keys and values (so values become keys and keys become values). The original code assigns value to the object's key position, +rather than assigning key to the position indexed by value (invertedObj[value] = key). */ + +// f) Fix the implementation of invert (and write tests to prove it's fixed!) + +function invert(obj) { + // Guard clause for non-object inputs + if (typeof obj !== "object" || obj === null || Array.isArray(obj)) { + return {}; + } + + const invertedObj = {}; + + for (const [key, value] of Object.entries(obj)) { + // Use bracket notation to dynamically set the property name to 'value' + // and assign 'key' as its value + invertedObj[value] = key; + } + + return invertedObj; +} + +module.exports = invert; From 01e11eca201edcfe3dc223154e30221779997d3e Mon Sep 17 00:00:00 2001 From: TTiamiyu Date: Tue, 28 Jul 2026 00:21:44 +0100 Subject: [PATCH 13/13] Add tests for invert function to validate key-value swapping and handle invalid inputs --- Sprint-2/interpret/invert.test.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 Sprint-2/interpret/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..bd61706a8 --- /dev/null +++ b/Sprint-2/interpret/invert.test.js @@ -0,0 +1,22 @@ +const invert = require("./invert.js"); + +describe("invert()", () => { + test("swaps keys and values for a single key-value pair", () => { + expect(invert({ a: 1 })).toEqual({ 1: "a" }); + }); + + test("swaps keys and values for multiple key-value pairs", () => { + expect(invert({ x: 10, y: 20 })).toEqual({ 10: "x", 20: "y" }); + expect(invert({ a: "1", b: "2" })).toEqual({ 1: "a", 2: "b" }); + }); + + test("handles empty objects", () => { + expect(invert({})).toEqual({}); + }); + + test("handles non-object invalid inputs gracefully", () => { + expect(invert(null)).toEqual({}); + expect(invert(["a", "b"])).toEqual({}); + expect(invert("string")).toEqual({}); + }); +});