From a48c9ea22d9d89e8a4a4ea731458bdebaa36e7bb Mon Sep 17 00:00:00 2001 From: Sandani Kannangara Date: Mon, 6 Jul 2026 02:15:16 +0100 Subject: [PATCH 01/19] Fix object property access in address.js --- Sprint-2/debug/address.js | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/Sprint-2/debug/address.js b/Sprint-2/debug/address.js index 940a6af83..b3051b28e 100644 --- a/Sprint-2/debug/address.js +++ b/Sprint-2/debug/address.js @@ -4,6 +4,27 @@ // but it isn't working... // Fix anything that isn't working +/*const address = { + houseNumber: 42, + street: "Imaginary Road", + city: "Manchester", + country: "England", + postcode: "XYZ 123", +}; + +console.log(`My house number is ${address[0]}`);*/ + +//--------------------------------------------------------------------------------- + +/*Predict and explain first... +address is an object, not an array. +Objects don’t have numeric indexes like [0]. +So address[0] returns undefined. +That’s why the output would be: +My house number is undefined +*/ + +//Fixed code const address = { houseNumber: 42, street: "Imaginary Road", @@ -12,4 +33,4 @@ const address = { postcode: "XYZ 123", }; -console.log(`My house number is ${address[0]}`); +console.log(`My house number is ${address.houseNumber}`); From c82a59f71f86ec70a5086c1b1cb02837e9f17f87 Mon Sep 17 00:00:00 2001 From: Sandani Kannangara Date: Mon, 6 Jul 2026 02:36:48 +0100 Subject: [PATCH 02/19] Fix for...of loop in author.js using Object.values() --- Sprint-2/debug/author.js | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/Sprint-2/debug/author.js b/Sprint-2/debug/author.js index 8c2125977..03b722310 100644 --- a/Sprint-2/debug/author.js +++ b/Sprint-2/debug/author.js @@ -3,7 +3,7 @@ // 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 -const author = { +/*const author = { firstName: "Zadie", lastName: "Smith", occupation: "writer", @@ -13,4 +13,30 @@ const author = { for (const value of author) { console.log(value); +}*/ + +//------------------------------------------------------------------------------------------ + +// Prediction and explanation + +/*This gives an error because: +author is a plain object +for...of only works with iterables +Objects are not iterable by default +So JavaScript throws: +TypeError: author is not iterable + */ + +//fixed code + +const author = { + firstName: "Zadie", + lastName: "Smith", + occupation: "writer", + age: 40, + alive: true, +}; + +for (const value of Object.values(author)) { + console.log(value); } From dd14256e984e255425708627501550ce63c11f26 Mon Sep 17 00:00:00 2001 From: Sandani Kannangara Date: Mon, 6 Jul 2026 03:15:10 +0100 Subject: [PATCH 03/19] Fix object printing in template literal in recipe.js --- Sprint-2/debug/recipe.js | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/Sprint-2/debug/recipe.js b/Sprint-2/debug/recipe.js index 6cbdd22cd..2d654eb11 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -4,7 +4,7 @@ // Each ingredient should be logged on a new line // How can you fix it? -const recipe = { +/*const recipe = { title: "bruschetta", serves: 2, ingredients: ["olive oil", "tomatoes", "salt", "pepper"], @@ -12,4 +12,37 @@ const recipe = { console.log(`${recipe.title} serves ${recipe.serves} ingredients: -${recipe}`); +${recipe}`);*/ + +//---------------------------------------------------------------------------------------------------------- + +// Prediction and explanation +/*The output will be: +bruschetta serves 2 + ingredients: +[object Object] + +The template literal creates a single string. +${recipe.title} is replaced with "bruschetta". +${recipe.serves} is replaced with 2. +${recipe} is an object, so JavaScript must convert it to a string before inserting it into the template literal. +A plain object's default string representation is "[object Object]". + +So JavaScript effectively creates this string before passing it to console.log(): + +bruschetta serves 2 + ingredients: +[object Object] + +If the goal is to print each ingredient, we need to access the ingredients array (recipe.ingredients) instead of the whole recipe object. */ + +//Fixed code + +const recipe = { + title: "bruschetta", + serves: 2, + ingredients: ["olive oil", "tomatoes", "salt", "pepper"], +}; + +console.log(`${recipe.title} serves ${recipe.serves} ingredients:`); +recipe.ingredients.forEach((ingredient) => console.log(ingredient)); From 1b5d78e72a9317e32e8c779098160d8bc56d19ec Mon Sep 17 00:00:00 2001 From: Sandani Kannangara Date: Mon, 6 Jul 2026 14:14:49 +0100 Subject: [PATCH 04/19] add Jest tests for contains including empty objects and invalid input --- Sprint-2/implement/contains.test.js | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index 326bdb1f2..2227b3177 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -1,5 +1,24 @@ const contains = require("./contains.js"); +describe("contains", () => { + test("Returns true if the object contains the key", () => { + expect(contains({ a: 1, b: 2 }, "a")).toEqual(true); + expect(contains({ cat: 1, dog: 2, cow: 2 }, "cow")).toEqual(true); + }); + + test("Returns false if the object doesn't contain the key", () => { + expect(contains({ a: 1, b: 2 }, "c")).toEqual(false); + expect(contains({ cat: 1, dog: 2, cow: 2 }, "pig")).toEqual(false); + }); + + test("Returns false if the object is empty", () => { + expect(contains({}, "c")).toEqual(false); + }); + test("Returns false if the input is invalid", () => { + expect(contains([1, 2, 3, 4], 3)).toEqual(false); + }); +}); + /* Implement a function called contains that checks an object contains a particular property @@ -20,7 +39,7 @@ 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.todo("contains on empty object returns false"); // Given an object with properties // When passed to contains with an existing property name From 787917824dfb093180690d0a9d3f5682ecda740f Mon Sep 17 00:00:00 2001 From: Sandani Kannangara Date: Mon, 6 Jul 2026 17:18:47 +0100 Subject: [PATCH 05/19] implement contains function and add invalid input test cases --- Sprint-2/implement/contains.js | 7 ++++++- Sprint-2/implement/contains.test.js | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Sprint-2/implement/contains.js b/Sprint-2/implement/contains.js index cd779308a..aa3980d0f 100644 --- a/Sprint-2/implement/contains.js +++ b/Sprint-2/implement/contains.js @@ -1,3 +1,8 @@ -function contains() {} +function contains(obj, val) { + if (typeof obj !== "object" || obj === null || Array.isArray(obj)) { + return false; + } + return Object.keys(obj).includes(val); +} module.exports = contains; diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index 2227b3177..f3f9e3302 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -16,6 +16,8 @@ describe("contains", () => { }); test("Returns false if the input is invalid", () => { expect(contains([1, 2, 3, 4], 3)).toEqual(false); + expect(contains(null, 3)).toEqual(false); + expect(contains("Hello World", "o")).toEqual(false); }); }); From c11d992175dc650d2f7035f6f1fe3985e3299a48 Mon Sep 17 00:00:00 2001 From: Sandani Kannangara Date: Mon, 6 Jul 2026 19:15:14 +0100 Subject: [PATCH 06/19] add Jest tests for createLookup function --- Sprint-2/implement/lookup.test.js | 48 ++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/Sprint-2/implement/lookup.test.js b/Sprint-2/implement/lookup.test.js index 547e06c5a..124453041 100644 --- a/Sprint-2/implement/lookup.test.js +++ b/Sprint-2/implement/lookup.test.js @@ -1,6 +1,6 @@ const createLookup = require("./lookup.js"); -test.todo("creates a country currency code lookup for multiple codes"); +//test.todo("creates a country currency code lookup for multiple codes"); /* @@ -33,3 +33,49 @@ It should return: 'CA': 'CAD' } */ + +describe("createLookup", () => { + test("Return an object with country codes and currency codes", () => { + const input = [ + ["US", "USD"], + ["CA", "CAD"], + ]; + const output = { + US: "USD", + CA: "CAD", + }; + expect(createLookup(input)).toEqual(output); + }); + test("Ignores invalid inner arrays", () => { + const input = [["US", "USD"], "Hello", ["CA", "CAD"]]; + const output = { + US: "USD", + CA: "CAD", + }; + expect(createLookup(input)).toEqual(output); + }); + test("returns empty object for empty inner arrays", () => { + expect(createLookup([[]])).toEqual({}); + }); + test("Handle repeat keys", () => { + const input = [ + ["US", "USD"], + ["CA", "CAD"], + ["US", "US-Dollar"], + ]; + const output = { + US: "US-Dollar", + CA: "CAD", + }; + expect(createLookup(input)).toEqual(output); + }); + + test("Return an empty object with invalid input", () => { + expect(() => createLookup(null)).toThrow(); + expect(() => createLookup("Hello World")).toThrow(); + expect(() => createLookup({})).toThrow(); + }); + test("Return an empty object with empty input", () => { + expect(createLookup([])).toEqual({}); + }); +}); From 2cb0162231f2bcd2fe3bf32a23bc712dcd6589d4 Mon Sep 17 00:00:00 2001 From: Sandani Kannangara Date: Mon, 6 Jul 2026 19:55:12 +0100 Subject: [PATCH 07/19] implement createLookup function --- Sprint-2/implement/lookup.js | 14 ++++++++++++-- Sprint-2/implement/lookup.test.js | 4 ++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Sprint-2/implement/lookup.js b/Sprint-2/implement/lookup.js index a6746e07f..7436745f2 100644 --- a/Sprint-2/implement/lookup.js +++ b/Sprint-2/implement/lookup.js @@ -1,5 +1,15 @@ -function createLookup() { - // implementation here +function createLookup(countryCurrencyPairs) { + let newObj = {}; + if (!Array.isArray(countryCurrencyPairs)) { + throw new Error("Input must be an array"); + } + + countryCurrencyPairs.forEach((pair) => { + if (Array.isArray(pair) && pair.length === 2) { + newObj[pair[0]] = pair[1]; + } + }); + return newObj; } module.exports = createLookup; diff --git a/Sprint-2/implement/lookup.test.js b/Sprint-2/implement/lookup.test.js index 124453041..e7d87c67c 100644 --- a/Sprint-2/implement/lookup.test.js +++ b/Sprint-2/implement/lookup.test.js @@ -46,7 +46,7 @@ describe("createLookup", () => { }; expect(createLookup(input)).toEqual(output); }); - test("Ignores invalid inner arrays", () => { + test("Ignores invalid inner array values", () => { const input = [["US", "USD"], "Hello", ["CA", "CAD"]]; const output = { US: "USD", @@ -70,7 +70,7 @@ describe("createLookup", () => { expect(createLookup(input)).toEqual(output); }); - test("Return an empty object with invalid input", () => { + test("Throws an error for invalid input", () => { expect(() => createLookup(null)).toThrow(); expect(() => createLookup("Hello World")).toThrow(); expect(() => createLookup({})).toThrow(); From dcc86626295dcfcf226132e0a72b9d3feaa2aeef Mon Sep 17 00:00:00 2001 From: Sandani Kannangara Date: Tue, 7 Jul 2026 01:18:12 +0100 Subject: [PATCH 08/19] implement parseQueryString with edge case handling --- Sprint-2/implement/querystring.js | 35 +++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 45ec4e5f3..58dddb9c4 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -3,14 +3,45 @@ function parseQueryString(queryString) { 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 pairWithoutAdd = pair.replace(/\+/g, " "); + const indexOfEqual = pairWithoutAdd.indexOf("="); + + let keyVal; + let val; + + if (indexOfEqual === -1) { + keyVal = pairWithoutAdd; + val = ""; + } else { + keyVal = pairWithoutAdd.slice(0, indexOfEqual); + val = pairWithoutAdd.slice(indexOfEqual + 1); + } + keyVal = decodeURIComponent(keyVal); + val = decodeURIComponent(val); + + if (Object.hasOwn(queryParams, keyVal)) { + if (!Array.isArray(queryParams[keyVal])) { + queryParams[keyVal] = [queryParams[keyVal]]; + } + queryParams[keyVal].push(val); + } else { + queryParams[keyVal] = val; + } } return queryParams; } module.exports = parseQueryString; + +/* +let a = { color: "red" }; +a.color = [a.color]; +console.log(a);*/ From 43d9b1f3c27869b080c95561af9c01653ce7c00a Mon Sep 17 00:00:00 2001 From: Sandani Kannangara Date: Tue, 7 Jul 2026 02:29:44 +0100 Subject: [PATCH 09/19] add Jest tests for tally function edge cases --- Sprint-2/implement/tally.test.js | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/Sprint-2/implement/tally.test.js b/Sprint-2/implement/tally.test.js index 2ceffa8dd..40cfe5523 100644 --- a/Sprint-2/implement/tally.test.js +++ b/Sprint-2/implement/tally.test.js @@ -23,7 +23,7 @@ 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.todo("tally on an empty array returns an empty object"); // Given an array with duplicate items // When passed to tally @@ -32,3 +32,31 @@ test.todo("tally on an empty array returns an empty object"); // Given an invalid input like a string // When passed to tally // Then it should throw an error + +describe("tally", () => { + test("Return an empty object for empty array input", () => { + expect(tally([])).toEqual({}); + }); + test("Return an object containing the count for each unique item", () => { + expect(tally(["cat"])).toEqual({ cat: 1 }); + expect(tally(["cat", "dog", "rat", "duck"])).toEqual({ + cat: 1, + dog: 1, + rat: 1, + duck: 1, + }); + }); + test("Return an object containing the count for duplicate items", () => { + expect(tally(["cat", "dog", "rat", "cat", "duck", "dog"])).toEqual({ + cat: 2, + dog: 2, + rat: 1, + duck: 1, + }); + }); + test("Throw an error for invalid input", () => { + expect(() => tally("Hello World")).toThrow("Invalid input"); + expect(() => tally({ cat: 3 })).toThrow("Invalid input"); + expect(() => tally(null)).toThrow("Invalid input"); + }); +}); From a1915e0b816641c1bef22307960e8c1271cdfc74 Mon Sep 17 00:00:00 2001 From: Sandani Kannangara Date: Tue, 7 Jul 2026 02:51:55 +0100 Subject: [PATCH 10/19] implement tally function and add integer array test cases --- Sprint-2/implement/tally.js | 12 ++++++++++-- Sprint-2/implement/tally.test.js | 6 +++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index f47321812..ae408126d 100644 --- a/Sprint-2/implement/tally.js +++ b/Sprint-2/implement/tally.js @@ -1,3 +1,11 @@ -function tally() {} - +function tally(input) { + const obj = {}; + if (input === null || !Array.isArray(input)) { + throw new Error("Invalid input"); + } + input.forEach((el) => { + obj[el] = (obj[el] || 0) + 1; + }); + return obj; +} module.exports = tally; diff --git a/Sprint-2/implement/tally.test.js b/Sprint-2/implement/tally.test.js index 40cfe5523..46af5d381 100644 --- a/Sprint-2/implement/tally.test.js +++ b/Sprint-2/implement/tally.test.js @@ -53,8 +53,12 @@ describe("tally", () => { rat: 1, duck: 1, }); + expect(tally([1, 2, 1])).toEqual({ + 1: 2, + 2: 1, + }); }); - test("Throw an error for invalid input", () => { + test("throws an error for invalid input", () => { expect(() => tally("Hello World")).toThrow("Invalid input"); expect(() => tally({ cat: 3 })).toThrow("Invalid input"); expect(() => tally(null)).toThrow("Invalid input"); From 3441e66331a79d93b8308442b55589b4de7f3237 Mon Sep 17 00:00:00 2001 From: Sandani Kannangara Date: Tue, 7 Jul 2026 03:48:23 +0100 Subject: [PATCH 11/19] Implement invert function and add explanation --- Sprint-2/interpret/invert.js | 41 ++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/Sprint-2/interpret/invert.js b/Sprint-2/interpret/invert.js index bb353fb1f..c84b3232a 100644 --- a/Sprint-2/interpret/invert.js +++ b/Sprint-2/interpret/invert.js @@ -6,7 +6,7 @@ // E.g. invert({x : 10, y : 20}), target output: {"10": "x", "20": "y"} -function invert(obj) { +/*function invert(obj) { const invertedObj = {}; for (const [key, value] of Object.entries(obj)) { @@ -14,16 +14,53 @@ function invert(obj) { } 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, +/* [["a", 1],["b", 2]] + + It is needed because the program needs to access both the key and the value of each object property in order to invert them. + Object.entries() gives us each key-value pair together, so we can swap them + */ + +//------------------------------------------------------------------------------------------ // d) Explain why the current return value is different from the target output +/*The current return value is different from the target output because key is a variable, +but invertedObj.key does not use the variable's value. +In JavaScript, dot notation treats key as a literal property name, so it creates a property called "key" in the object. +To use the value stored inside the key variable as the property name, we need bracket notation: invertedObj[key]. */ + +//------------------------------------------------------------------------------------------ // e) Fix the implementation of invert (and write tests to prove it's fixed!) + +function invert(obj) { + const invertedObj = {}; + + for (const [key, value] of Object.entries(obj)) { + invertedObj[value] = key; + } + + return invertedObj; +} + +console.log(invert({ a: 1, b: 2 })); From a34e8833906c0137f07776fdd7a486b33ec4e79e Mon Sep 17 00:00:00 2001 From: Sandani Kannangara Date: Tue, 7 Jul 2026 04:54:08 +0100 Subject: [PATCH 12/19] add full Jest tests and invalid input handling for invert --- Sprint-2/interpret/invert.js | 7 +++++-- Sprint-2/interpret/invert.test.js | 24 ++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 Sprint-2/interpret/invert.test.js diff --git a/Sprint-2/interpret/invert.js b/Sprint-2/interpret/invert.js index c84b3232a..11a8c7c29 100644 --- a/Sprint-2/interpret/invert.js +++ b/Sprint-2/interpret/invert.js @@ -56,11 +56,14 @@ To use the value stored inside the key variable as the property name, we need br function invert(obj) { const invertedObj = {}; + if (typeof obj !== "object" || obj === null || Array.isArray(obj)) { + throw new Error("Invalid input"); + } + for (const [key, value] of Object.entries(obj)) { invertedObj[value] = key; } return invertedObj; } - -console.log(invert({ a: 1, b: 2 })); +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..cab1e208a --- /dev/null +++ b/Sprint-2/interpret/invert.test.js @@ -0,0 +1,24 @@ +const invert = require("./invert.js"); +describe("invert", () => { + test("returns error for an invalid input", () => { + expect(() => invert("Hello World")).toThrow("Invalid input"); + expect(() => invert(["cat", "dog", "pig"])).toThrow("Invalid input"); + expect(() => invert(null)).toThrow("Invalid input"); + }); + + test("returns an empty object for an empty object input", () => { + expect(invert({})).toEqual({}); + }); + + test("handles an object with a single property", () => { + expect(invert({ cat: 3 })).toEqual({ 3: "cat" }); + }); + + test("returns an object with keys and values swapped", () => { + expect(invert({ cat: 3, dog: 2 })).toEqual({ 3: "cat", 2: "dog" }); + }); + + test("handles duplicate values by keeping the last key", () => { + expect(invert({ cat: 3, dog: 2, pig: 3 })).toEqual({ 3: "pig", 2: "dog" }); + }); +}); From 0fa3671644901aac85ef72d3fe0d52d3a85e6083 Mon Sep 17 00:00:00 2001 From: Sandani Kannangara Date: Tue, 7 Jul 2026 23:00:10 +0100 Subject: [PATCH 13/19] split calculateMode into smaller functions --- Sprint-2/stretch/mode.js | 41 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/Sprint-2/stretch/mode.js b/Sprint-2/stretch/mode.js index 3f7609d79..445fdc079 100644 --- a/Sprint-2/stretch/mode.js +++ b/Sprint-2/stretch/mode.js @@ -8,9 +8,9 @@ // refactor calculateMode by splitting up the code // into smaller functions using the stages above -function calculateMode(list) { - // track frequency of each value - let freqs = new Map(); +//function calculateMode(list) { +// track frequency of each value +/*let freqs = new Map(); for (let num of list) { if (typeof num !== "number") { @@ -19,8 +19,10 @@ function calculateMode(list) { freqs.set(num, (freqs.get(num) || 0) + 1); } +*/ - // Find the value with the highest frequency +// Find the value with the highest frequency +/* let maxFreq = 0; let mode; for (let [num, freq] of freqs) { @@ -31,6 +33,37 @@ function calculateMode(list) { } return maxFreq === 0 ? NaN : mode; + return true; } +calculateMode([1, 3, "2", 2, 3, null]); + +module.exports = calculateMode;*/ + +function calculateFrequencies(list) { + const freqs = new Map(); + + for (let num of list) { + if (typeof num === "number") { + freqs.set(num, (freqs.get(num) || 0) + 1); + } + } + return freqs; +} +function findMode(freqs) { + let maxFreq = 0; + let mode; + for (let [num, freq] of freqs) { + if (freq > maxFreq) { + maxFreq = freq; + mode = num; + } + } + return maxFreq === 0 ? NaN : mode; +} + +function calculateMode(list) { + const freqs = calculateFrequencies(list); + return findMode(freqs); +} module.exports = calculateMode; From fdb0736d2a670fa4649f5c5278c046fe9a319822 Mon Sep 17 00:00:00 2001 From: Sandani Kannangara Date: Tue, 7 Jul 2026 23:42:57 +0100 Subject: [PATCH 14/19] implement countWords function with edge case handling --- Sprint-2/stretch/count-words.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Sprint-2/stretch/count-words.js b/Sprint-2/stretch/count-words.js index 8e85d19d7..0dfe8293c 100644 --- a/Sprint-2/stretch/count-words.js +++ b/Sprint-2/stretch/count-words.js @@ -26,3 +26,19 @@ 3. Order the results to find out which word is the most common in the input */ + +function countWords(Str) { + if (Str.trim() === "" || typeof Str !== "string") { + return {}; + } + + const stringSet = Str.trim() + .toLowerCase() + .replace(/[.,!?]/g, "") + .split(/\s+/); + const wordObject = {}; + for (const el of stringSet) { + wordObject[el] = (wordObject[el] || 0) + 1; + } + return wordObject; +} From 6dc4d28e3085cc51fe1084831505c61a33e326a7 Mon Sep 17 00:00:00 2001 From: Sandani Kannangara Date: Wed, 8 Jul 2026 00:05:07 +0100 Subject: [PATCH 15/19] implement totalTill coin value calculation --- Sprint-2/stretch/till.js | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/Sprint-2/stretch/till.js b/Sprint-2/stretch/till.js index 6a08532e7..e56296d80 100644 --- a/Sprint-2/stretch/till.js +++ b/Sprint-2/stretch/till.js @@ -3,7 +3,7 @@ // Given an object of coins // When this till object is passed to totalTill // Then it should return the total amount in pounds - +/* function totalTill(till) { let total = 0; @@ -21,11 +21,39 @@ const till = { "20p": 10, }; const totalAmount = totalTill(till); +console.log(totalAmount);*/ + +//----------------------------------------------------------------------------------------------------- // a) What is the target output when totalTill is called with the till object +// £4.40 // b) Why do we need to use Object.entries inside the for...of loop in this function? +// Objects are not directly iterable, so we cannot use a for...of loop on an object. +// Object.entries converts the object into an array of key-value pairs. +// Each pair is an array containing [key, value], which allows us to use for...of with destructuring to access coin and quantity separately. // c) What does coin * quantity evaluate to inside the for...of loop? +// It calculates the total value of each type of coin by multiplying the coin value by its quantity. +// However, coin is initially a string like "50p", so it must be converted into a number first before multiplication. // d) Write a test for this function to check it works and then fix the implementation of totalTill + +function totalTill(till) { + let total = 0; + + for (const [coin, quantity] of Object.entries(till)) { + const coinValue = Number(coin.replace("p", "")); + total += coinValue * quantity; + } + + return `£${(total / 100).toFixed(2)}`; +} + +const till = { + "1p": 10, + "5p": 6, + "50p": 4, + "20p": 10, +}; +const totalAmount = totalTill(till); From c8e54f1244107c51b75349f8fb6ef640c163f909 Mon Sep 17 00:00:00 2001 From: Sandani Kannangara Date: Mon, 13 Jul 2026 14:16:43 +0100 Subject: [PATCH 16/19] Add for loop to recipe function based on review feedback --- Sprint-2/debug/recipe.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Sprint-2/debug/recipe.js b/Sprint-2/debug/recipe.js index 2d654eb11..24a3e8222 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -45,4 +45,7 @@ const recipe = { }; console.log(`${recipe.title} serves ${recipe.serves} ingredients:`); -recipe.ingredients.forEach((ingredient) => console.log(ingredient)); +//recipe.ingredients.forEach((ingredient) => console.log(ingredient)); +for (let ingredient of recipe.ingredients) { + console.log(ingredient); +} From 92d720cbb8fc5846aa7759aac893b544a48dd2ac Mon Sep 17 00:00:00 2001 From: Sandani Kannangara Date: Mon, 13 Jul 2026 15:04:48 +0100 Subject: [PATCH 17/19] Trigger CI check again From d6d7b68ddcb581df48448075889d45f057a0b7eb Mon Sep 17 00:00:00 2001 From: Sandani Kannangara Date: Mon, 13 Jul 2026 22:18:25 +0100 Subject: [PATCH 18/19] d edge case tests and update parseQueryString --- Sprint-2/implement/querystring.js | 55 +++++++++++--------------- Sprint-2/implement/querystring.test.js | 32 ++++++++++++++- 2 files changed, 53 insertions(+), 34 deletions(-) diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 58dddb9c4..b5a7e631c 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -1,38 +1,32 @@ function parseQueryString(queryString) { const queryParams = {}; - if (queryString.length === 0) { - return queryParams; + if (typeof queryString !== "string") { + throw new Error("Invalid input"); } - - const keyValuePairs = queryString.split("&"); - - for (const pair of keyValuePairs) { - if (pair === "") { - continue; - } - const pairWithoutAdd = pair.replace(/\+/g, " "); - const indexOfEqual = pairWithoutAdd.indexOf("="); - - let keyVal; - let val; - - if (indexOfEqual === -1) { - keyVal = pairWithoutAdd; - val = ""; + if (queryString.length === 0) return queryParams; + + const parseQueryStringArray = queryString.replace(/\+/g, " ").split("&"); + let key; + let value; + for (const pair of parseQueryStringArray) { + if (pair === "") continue; + let equalIndex = pair.indexOf("="); + if (equalIndex === -1) { + key = pair; + value = ""; } else { - keyVal = pairWithoutAdd.slice(0, indexOfEqual); - val = pairWithoutAdd.slice(indexOfEqual + 1); + key = decodeURIComponent(pair.slice(0, equalIndex)); + value = decodeURIComponent(pair.slice(equalIndex + 1)); } - keyVal = decodeURIComponent(keyVal); - val = decodeURIComponent(val); - - if (Object.hasOwn(queryParams, keyVal)) { - if (!Array.isArray(queryParams[keyVal])) { - queryParams[keyVal] = [queryParams[keyVal]]; + if (Object.hasOwn(queryParams, key)) { + if (Array.isArray(queryParams[key])) { + queryParams[key].push(value); + } else { + queryParams[key] = [queryParams[key]]; + queryParams[key].push(value); } - queryParams[keyVal].push(val); } else { - queryParams[keyVal] = val; + queryParams[key] = value; } } @@ -40,8 +34,3 @@ function parseQueryString(queryString) { } module.exports = parseQueryString; - -/* -let a = { color: "red" }; -a.color = [a.color]; -console.log(a);*/ diff --git a/Sprint-2/implement/querystring.test.js b/Sprint-2/implement/querystring.test.js index 328b8df61..37bd6b2e5 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({ @@ -25,6 +25,36 @@ test("should accept empty string as key or as value", () => { expect(parseQueryString("=")).toEqual({ "": "" }); }); +test("should return empty object for empty input", () => { + expect(parseQueryString("")).toEqual({}); +}); + +test("throws an error for invalid input", () => { + expect(() => parseQueryString(null)).toThrow("Invalid input"); + expect(() => parseQueryString({})).toThrow("Invalid input"); + expect(() => parseQueryString([])).toThrow("Invalid input"); + expect(() => parseQueryString(undefined)).toThrow("Invalid input"); + expect(() => parseQueryString(123)).toThrow("Invalid input"); + expect(() => parseQueryString(true)).toThrow("Invalid input"); +}); + +test("should work on duplicate empty keys", () => { + expect(parseQueryString("=one&=two")).toEqual({ + "": ["one", "two"], + }); +}); +test("should work on duplicate empty values", () => { + expect(parseQueryString("key=&key=")).toEqual({ + key: ["", ""], + }); +}); + +test("should work on & inside values", () => { + expect(parseQueryString("text=Tom%26Jerry")).toEqual({ + text: "Tom&Jerry", + }); +}); + test("should decode percent-encoded characters", () => { expect(parseQueryString("%24half=1%2F2")).toEqual({ $half: "1/2", From a3f87104d87aed89c56a3177ed2f002f9278578a Mon Sep 17 00:00:00 2001 From: Sandani Kannangara Date: Tue, 14 Jul 2026 20:45:40 +0100 Subject: [PATCH 19/19] Update recipe with separate print function --- Sprint-2/debug/recipe.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Sprint-2/debug/recipe.js b/Sprint-2/debug/recipe.js index 24a3e8222..81cafba92 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -45,7 +45,8 @@ const recipe = { }; console.log(`${recipe.title} serves ${recipe.serves} ingredients:`); -//recipe.ingredients.forEach((ingredient) => console.log(ingredient)); -for (let ingredient of recipe.ingredients) { - console.log(ingredient); +recipe.ingredients.forEach(printIngredients); + +function printIngredients(item) { + console.log(item); }