From ca2ad9f5d8c6013ed5ec37e9fd4e4ddd3acab311 Mon Sep 17 00:00:00 2001 From: joanne342 <130585013+joanne342@users.noreply.github.com> Date: Thu, 9 Jul 2026 09:10:11 +0100 Subject: [PATCH 01/19] Update address.js --- Sprint-2/debug/address.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sprint-2/debug/address.js b/Sprint-2/debug/address.js index 940a6af83..fde180d6d 100644 --- a/Sprint-2/debug/address.js +++ b/Sprint-2/debug/address.js @@ -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"]}`); From 4048bc9a3e39bd14f2082714ed34b692566bc0af Mon Sep 17 00:00:00 2001 From: joanne342 <130585013+joanne342@users.noreply.github.com> Date: Thu, 9 Jul 2026 11:03:23 +0100 Subject: [PATCH 02/19] Update author.js --- Sprint-2/debug/author.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sprint-2/debug/author.js b/Sprint-2/debug/author.js index 8c2125977..7ba8f4c75 100644 --- a/Sprint-2/debug/author.js +++ b/Sprint-2/debug/author.js @@ -11,6 +11,6 @@ const author = { alive: true, }; -for (const value of author) { +for (const value of Object.values(author)) { console.log(value); } From d91daaa454b0bcf4f2ecbd497e0311f32818fa6a Mon Sep 17 00:00:00 2001 From: joanne342 <130585013+joanne342@users.noreply.github.com> Date: Thu, 9 Jul 2026 11:07:46 +0100 Subject: [PATCH 03/19] Update recipe.js --- Sprint-2/debug/recipe.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sprint-2/debug/recipe.js b/Sprint-2/debug/recipe.js index 6cbdd22cd..a413b3332 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -12,4 +12,4 @@ const recipe = { console.log(`${recipe.title} serves ${recipe.serves} ingredients: -${recipe}`); +${recipe.ingredients.join("\n")}`); From cc230d3ad5f601a48f00e1cf59e27f679ff916e9 Mon Sep 17 00:00:00 2001 From: joanne342 <130585013+joanne342@users.noreply.github.com> Date: Thu, 9 Jul 2026 11:16:53 +0100 Subject: [PATCH 04/19] Update contains.test.js --- Sprint-2/implement/contains.test.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index 326bdb1f2..aa1f35ebe 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -20,16 +20,28 @@ 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("contains on empty object returns false", () => { + 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 object contains an existing property", () => { + 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 object does not contain the property", () => { + 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 or throws an error when passed invalid parameters like an array", () => { + expect(() => contains([], "a")).not.toThrow(); + expect(contains([], "a")).toBe(false); +}); From 5325eb452b982d4f1083f438e1bd918fb5ce273f Mon Sep 17 00:00:00 2001 From: joanne342 <130585013+joanne342@users.noreply.github.com> Date: Thu, 9 Jul 2026 11:21:59 +0100 Subject: [PATCH 05/19] Update contains.js --- Sprint-2/implement/contains.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sprint-2/implement/contains.js b/Sprint-2/implement/contains.js index cd779308a..51063ecd2 100644 --- a/Sprint-2/implement/contains.js +++ b/Sprint-2/implement/contains.js @@ -1,3 +1,5 @@ -function contains() {} +function contains() { + return property_name in obj; +} module.exports = contains; From 8ed4137b6e3652cc943b8270942d554835cc338b Mon Sep 17 00:00:00 2001 From: joanne342 <130585013+joanne342@users.noreply.github.com> Date: Thu, 9 Jul 2026 11:31:17 +0100 Subject: [PATCH 06/19] Update lookup.test.js --- Sprint-2/implement/lookup.test.js | 34 +++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/Sprint-2/implement/lookup.test.js b/Sprint-2/implement/lookup.test.js index 547e06c5a..3bb11ac1d 100644 --- a/Sprint-2/implement/lookup.test.js +++ b/Sprint-2/implement/lookup.test.js @@ -33,3 +33,37 @@ It should return: 'CA': 'CAD' } */ + +test("creates a country currency code lookup for multiple codes", () => { + const countryCurrencyPairs = [ + ["US", "USD"], + ["CA", "CAD"], + ]; + + const result = createLookup(countryCurrencyPairs); + + expect(result).toEqual({ + US: "USD", + CA: "CAD", + }); +}); + +test("creates a country currency code lookup for a single code", () => { + const countryCurrencyPairs = [ + ["GB", "GBP"], + ]; + + const result = createLookup(countryCurrencyPairs); + + expect(result).toEqual({ + GB: "GBP", + }); +}); + +test("returns an empty object when given an empty array", () => { + const countryCurrencyPairs = []; + + const result = createLookup(countryCurrencyPairs); + + expect(result).toEqual({}); +}); From caa36799abf08c4e22526b6d2ddf08e800721b68 Mon Sep 17 00:00:00 2001 From: joanne342 <130585013+joanne342@users.noreply.github.com> Date: Thu, 9 Jul 2026 11:38:34 +0100 Subject: [PATCH 07/19] Update lookup.js --- Sprint-2/implement/lookup.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Sprint-2/implement/lookup.js b/Sprint-2/implement/lookup.js index a6746e07f..6929ca228 100644 --- a/Sprint-2/implement/lookup.js +++ b/Sprint-2/implement/lookup.js @@ -1,5 +1,10 @@ function createLookup() { // implementation here + for (const [country, currency] of country_currency_pairs) { + lookup[country] = currency; + } + + return lookup; } module.exports = createLookup; From 06a8a411e14b0e8d1d1b354ab18b33561ce36e99 Mon Sep 17 00:00:00 2001 From: joanne342 <130585013+joanne342@users.noreply.github.com> Date: Thu, 9 Jul 2026 13:20:31 +0100 Subject: [PATCH 08/19] Update querystring.test.js --- Sprint-2/implement/querystring.test.js | 62 +++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 6 deletions(-) diff --git a/Sprint-2/implement/querystring.test.js b/Sprint-2/implement/querystring.test.js index 328b8df61..909f04abc 100644 --- a/Sprint-2/implement/querystring.test.js +++ b/Sprint-2/implement/querystring.test.js @@ -37,12 +37,62 @@ test("should replace '+' by ' '", () => { }); }); -// Stretch exercise: Handling query strings that contain identical keys +test("should parse basic key-value pairs", () => { + expect(parseQueryString("name=John&age=30")).toEqual({ + name: "John", + age: "30", + }); +}); + +test("should replace '+' by ' '", () => { + expect(parseQueryString("name=John+Doe&city=New+York")).toEqual({ + name: "John Doe", + city: "New York", + }); + + expect(parseQueryString("full+name=John+Doe")).toEqual({ + "full name": "John Doe", + }); +}); + +test("should decode percent-encoded characters", () => { + expect(parseQueryString("message=Hello%20World")).toEqual({ + message: "Hello World", + }); -// Delete this test if you are not working on this optional case -test("should store values of a key in an array when the key has 2 or more values", () => { - expect(parseQueryString("key=value1&key=value2&key=value3&foo=bar")).toEqual({ - key: ["value1", "value2", "value3"], - foo: "bar", + expect(parseQueryString("%24half=1%2F2")).toEqual({ + $half: "1/2", }); }); + +test("should handle keys without values", () => { + expect(parseQueryString("debug")).toEqual({ + debug: "", + }); + + expect(parseQueryString("key")).toEqual({ + key: "", + }); +}); + +test("should handle empty values", () => { + expect(parseQueryString("name=&age=25")).toEqual({ + name: "", + age: "25", + }); + + expect(parseQueryString("key=")).toEqual({ + key: "", + }); +}); + +test("should return empty object for empty input", () => { + expect(parseQueryString("")).toEqual({}); +}); + +test("should use the last value when duplicate keys exist", () => { + expect(parseQueryString("color=red&color=blue")).toEqual({ + color: "blue", + }); +}); + From 0eed554d3306314cfe89708e811b952adc747e96 Mon Sep 17 00:00:00 2001 From: joanne342 <130585013+joanne342@users.noreply.github.com> Date: Thu, 9 Jul 2026 13:22:25 +0100 Subject: [PATCH 09/19] Update querystring.js --- Sprint-2/implement/querystring.js | 36 +++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 45ec4e5f3..54eb8ea5a 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -1,16 +1,34 @@ function parseQueryString(queryString) { - const queryParams = {}; - if (queryString.length === 0) { - return queryParams; - } - const keyValuePairs = queryString.split("&"); + if (queryString === "") { + return {}; + } else { + queryString = decodeURIComponent(queryString) + .replace(/\+/g, " ") + .replace(/&&/g, "&") + .replace(/&+$/, ""); + + const splitByAnds = queryString.split("&"); + + const splitByEquals = splitByAnds.map(x => { + const parts = x.split("=", 2); + return parts.length === 1 ? [parts[0], ""] : parts; + }); - for (const pair of keyValuePairs) { - const [key, value] = pair.split("="); - queryParams[key] = value; + return Object.fromEntries(splitByEquals); } +} + // 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; + // } - return queryParams; + // return queryParams; } module.exports = parseQueryString; From c5fc82b325cd92b945677fbe476f8de0a573f512 Mon Sep 17 00:00:00 2001 From: joanne342 <130585013+joanne342@users.noreply.github.com> Date: Thu, 9 Jul 2026 13:56:55 +0100 Subject: [PATCH 10/19] Update contains.js --- Sprint-2/implement/contains.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sprint-2/implement/contains.js b/Sprint-2/implement/contains.js index 51063ecd2..cf2d0757a 100644 --- a/Sprint-2/implement/contains.js +++ b/Sprint-2/implement/contains.js @@ -1,4 +1,4 @@ -function contains() { +function contains(obj, property_name) { return property_name in obj; } From 32a8ef2010eb3fbcd4c6d04428e51cdf96f54193 Mon Sep 17 00:00:00 2001 From: joanne342 <130585013+joanne342@users.noreply.github.com> Date: Thu, 9 Jul 2026 14:03:25 +0100 Subject: [PATCH 11/19] Update lookup.js --- Sprint-2/implement/lookup.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sprint-2/implement/lookup.js b/Sprint-2/implement/lookup.js index 6929ca228..4373213ac 100644 --- a/Sprint-2/implement/lookup.js +++ b/Sprint-2/implement/lookup.js @@ -1,5 +1,6 @@ -function createLookup() { +function createLookup(country_currency_pairs) { // implementation here + const lookup = {}; for (const [country, currency] of country_currency_pairs) { lookup[country] = currency; } From 47caa8c2ebf6fc99e7c761bb81c214d372c59683 Mon Sep 17 00:00:00 2001 From: joanne342 <130585013+joanne342@users.noreply.github.com> Date: Thu, 9 Jul 2026 14:04:08 +0100 Subject: [PATCH 12/19] Update lookup.test.js --- Sprint-2/implement/lookup.test.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/Sprint-2/implement/lookup.test.js b/Sprint-2/implement/lookup.test.js index 3bb11ac1d..beb33b5b3 100644 --- a/Sprint-2/implement/lookup.test.js +++ b/Sprint-2/implement/lookup.test.js @@ -1,7 +1,5 @@ const createLookup = require("./lookup.js"); -test.todo("creates a country currency code lookup for multiple codes"); - /* Create a lookup object of key value pairs from an array of code pairs From a0c43293b33e90b3e0d91f0fa35d2cf591b79394 Mon Sep 17 00:00:00 2001 From: joanne342 <130585013+joanne342@users.noreply.github.com> Date: Thu, 9 Jul 2026 14:06:23 +0100 Subject: [PATCH 13/19] Update querystring.js --- Sprint-2/implement/querystring.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 54eb8ea5a..6d04c413d 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -29,6 +29,6 @@ function parseQueryString(queryString) { // } // return queryParams; -} + module.exports = parseQueryString; From 896e52b8ecbe2ae6b6ba3938058097b360918bc9 Mon Sep 17 00:00:00 2001 From: joanne342 <130585013+joanne342@users.noreply.github.com> Date: Thu, 9 Jul 2026 14:46:10 +0100 Subject: [PATCH 14/19] Update querystring.js --- Sprint-2/implement/querystring.js | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 6d04c413d..c825d8cef 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -10,25 +10,16 @@ function parseQueryString(queryString) { const splitByAnds = queryString.split("&"); const splitByEquals = splitByAnds.map(x => { - const parts = x.split("=", 2); - return parts.length === 1 ? [parts[0], ""] : parts; + const match = x.match(/^([^=]*)(?:=(.*))?$/); + + const key = match[1]; + const value = match[2] ?? ""; + + return [key, value]; }); return Object.fromEntries(splitByEquals); } } - // 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; - // } - - // return queryParams; - module.exports = parseQueryString; From f8ce3aaddb9abfd008fcc8efc701101e6892fc2d Mon Sep 17 00:00:00 2001 From: joanne342 <130585013+joanne342@users.noreply.github.com> Date: Thu, 9 Jul 2026 15:07:55 +0100 Subject: [PATCH 15/19] Update tally.test.js --- Sprint-2/implement/tally.test.js | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/Sprint-2/implement/tally.test.js b/Sprint-2/implement/tally.test.js index 2ceffa8dd..ea349000e 100644 --- a/Sprint-2/implement/tally.test.js +++ b/Sprint-2/implement/tally.test.js @@ -20,15 +20,36 @@ const tally = require("./tally.js"); // When passed an array of items // Then it should return an object containing the count for each unique item +test("tally counts the frequency of 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 duplicate items correctly", () => { + expect(tally(["a", "a", "a"])).toEqual({ + a: 3, + }); +}); + // Given an invalid input like a string // When passed to tally // Then it should throw an error + +test("tally throws an error when passed invalid input", () => { + expect(() => tally("a")).toThrow(); +}); From aaeb462d2a39f408a22f8313ffc0ae147e72cedb Mon Sep 17 00:00:00 2001 From: joanne342 <130585013+joanne342@users.noreply.github.com> Date: Thu, 9 Jul 2026 15:27:05 +0100 Subject: [PATCH 16/19] Update tally.js --- Sprint-2/implement/tally.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index f47321812..84b03e706 100644 --- a/Sprint-2/implement/tally.js +++ b/Sprint-2/implement/tally.js @@ -1,3 +1,16 @@ -function tally() {} +function tally(items) { + if (!Array.isArray(items)) { + throw new TypeError("Input must be an array"); + } + + const uniqueItems = [...new Set(items)].sort(); + + const counts = {}; + for (const item of uniqueItems) { + counts[item] = items.filter(x => x === item).length; + } + + return counts; +} module.exports = tally; From 99d8442a0b14f53c07ce5b5bae3304675cdd6c7c Mon Sep 17 00:00:00 2001 From: joanne342 <130585013+joanne342@users.noreply.github.com> Date: Fri, 10 Jul 2026 08:06:59 +0100 Subject: [PATCH 17/19] Update invert.js --- Sprint-2/interpret/invert.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sprint-2/interpret/invert.js b/Sprint-2/interpret/invert.js index bb353fb1f..f04b2fb08 100644 --- a/Sprint-2/interpret/invert.js +++ b/Sprint-2/interpret/invert.js @@ -9,8 +9,8 @@ function invert(obj) { const invertedObj = {}; - for (const [key, value] of Object.entries(obj)) { - invertedObj.key = value; + for (const [value, key] of Object.entries(obj)) { + invertedObj[key] = value; } return invertedObj; From ca1fe4e533070f77a568ce3c3c17d96503ecc13f Mon Sep 17 00:00:00 2001 From: joanne342 <130585013+joanne342@users.noreply.github.com> Date: Fri, 10 Jul 2026 08:13:15 +0100 Subject: [PATCH 18/19] Create invert.test.js --- Sprint-2/interpret/invert.test.js | 1 + 1 file changed, 1 insertion(+) 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..8b1378917 --- /dev/null +++ b/Sprint-2/interpret/invert.test.js @@ -0,0 +1 @@ + From 13a400e9ff4ff3bbf55749768586dcd924333442 Mon Sep 17 00:00:00 2001 From: joanne342 <130585013+joanne342@users.noreply.github.com> Date: Fri, 10 Jul 2026 08:14:17 +0100 Subject: [PATCH 19/19] Update invert.test.js --- Sprint-2/interpret/invert.test.js | 45 +++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/Sprint-2/interpret/invert.test.js b/Sprint-2/interpret/invert.test.js index 8b1378917..fd92c1baf 100644 --- a/Sprint-2/interpret/invert.test.js +++ b/Sprint-2/interpret/invert.test.js @@ -1 +1,46 @@ +const invert = require("./invert.js"); +/* +Implement a function called invert that swaps the keys and values of an object. + +E.g. invert({ x: 10, y: 20 }) +// returns { "10": "x", "20": "y" } +*/ + +// Acceptance criteria: + +// Given an empty object +// When passed to invert +// Then it should return an empty object +test("returns an empty object when given an empty object", () => { + expect(invert({})).toEqual({}); +}); + +// Given an object with one property +// When passed to invert +// Then it should swap the key and value +test("inverts an object with one key-value pair", () => { + expect(invert({ a: 1 })).toEqual({ + "1": "a", + }); +}); + +// Given an object with multiple properties +// When passed to invert +// Then it should swap all keys and values +test("inverts an object with multiple key-value pairs", () => { + expect(invert({ a: 1, b: 2 })).toEqual({ + "1": "a", + "2": "b", + }); +}); + +// Given an object with string values +// When passed to invert +// Then it should swap the keys and values +test("inverts string values correctly", () => { + expect(invert({ first: "apple", second: "banana" })).toEqual({ + apple: "first", + banana: "second", + }); +});