From d4b2a29d77edc0b30b19811cf501f0fe22d6558e Mon Sep 17 00:00:00 2001 From: enjoy15 Date: Thu, 30 Oct 2025 21:55:34 +0000 Subject: [PATCH 01/14] update index to houseNumber --- 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..36d2f865d 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 4c0a9515c1659850f2325e4dbccdf8a20ab42ee8 Mon Sep 17 00:00:00 2001 From: enjoy15 Date: Thu, 30 Oct 2025 22:04:17 +0000 Subject: [PATCH 02/14] add comment and test with bracket notation --- Sprint-2/debug/address.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sprint-2/debug/address.js b/Sprint-2/debug/address.js index 36d2f865d..2aeb91c2d 100644 --- a/Sprint-2/debug/address.js +++ b/Sprint-2/debug/address.js @@ -3,6 +3,7 @@ // This code should log out the houseNumber from the address object // but it isn't working... // Fix anything that isn't working +// It is not working because the object properties should be accessed using dot notation or bracket notation. const address = { houseNumber: 42, @@ -12,4 +13,4 @@ const address = { postcode: "XYZ 123", }; -console.log(`My house number is ${address.houseNumber}`); +console.log(`My house number is ${address["houseNumber"]}`); From a77098b19bc9c2c82ef10921ff61354e92e68926 Mon Sep 17 00:00:00 2001 From: enjoy15 Date: Thu, 30 Oct 2025 22:14:09 +0000 Subject: [PATCH 03/14] debug author.js --- Sprint-2/debug/author.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Sprint-2/debug/author.js b/Sprint-2/debug/author.js index 8c2125977..388388184 100644 --- a/Sprint-2/debug/author.js +++ b/Sprint-2/debug/author.js @@ -1,4 +1,8 @@ // Predict and explain first... +// The for...of loop is used to iterate over iterable objects like arrays or strings. +// However, author is a plain object, which is not iterable by default. +// Therefore, using for...of directly on an object will result in a TypeError. +// To iterate over the values of an object, we can use Object.values(), Object.keys(), or a for...in loop. // 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 +15,10 @@ const author = { alive: true, }; -for (const value of author) { - console.log(value); -} +// for (const value of Object.values(author)) { +// console.log(value); +// } + +for (const key in author) { + console.log(author[key]); +} \ No newline at end of file From ada687780ba2206097848b55a8f70861c4bbf966 Mon Sep 17 00:00:00 2001 From: enjoy15 Date: Thu, 30 Oct 2025 22:19:11 +0000 Subject: [PATCH 04/14] debug recipe.js --- Sprint-2/debug/recipe.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Sprint-2/debug/recipe.js b/Sprint-2/debug/recipe.js index 6cbdd22cd..fa7db05ff 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -1,8 +1,7 @@ // Predict and explain first... // 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? +// const recipe = { title: "bruschetta", @@ -12,4 +11,4 @@ const recipe = { console.log(`${recipe.title} serves ${recipe.serves} ingredients: -${recipe}`); +${recipe.ingredients.join("\n")}`); From bbd9d5de4744fed093229a5ab6a9ddfa867bfbda Mon Sep 17 00:00:00 2001 From: enjoy15 Date: Fri, 31 Oct 2025 20:14:51 +0000 Subject: [PATCH 05/14] update contains and contains.test --- Sprint-2/implement/contains.js | 15 ++++++++++++++- Sprint-2/implement/contains.test.js | 20 +++++++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/Sprint-2/implement/contains.js b/Sprint-2/implement/contains.js index cd779308a..6420a8a14 100644 --- a/Sprint-2/implement/contains.js +++ b/Sprint-2/implement/contains.js @@ -1,3 +1,16 @@ -function contains() {} + + +function contains(obj, prop) { + if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) { + return false; + } + for (const key in obj) { + if (key === prop) { + return true; + } + } + return false; +} module.exports = contains; + diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index 326bdb1f2..2d239e028 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -17,19 +17,37 @@ as the object doesn't contains a key of 'c' // When passed an object and a property name // Then it should return true if the object contains the property, false otherwise + // 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("contains returns true for existing property", () => { + expect(contains({a: 1, b: 2}, 'a')).toBe(true); + expect(contains({x: 10, y: 20}, 'y')).toBe(true); +}); // Given an object with properties // When passed to contains with a non-existent property name // Then it should return false +test("contains returns false for non-existent property", () => { + expect(contains({a: 1, b: 2}, 'c')).toBe(false); + expect(contains({x: 10, y: 20}, 'z')).toBe(false); +}); // Given invalid parameters like an array // When passed to contains // Then it should return false or throw an error +test("contains returns false for invalid parameters", () => { + expect(contains([], 'a')).toBe(false); + expect(contains(null, 'a')).toBe(false); + expect(contains(undefined, 'a')).toBe(false); + expect(contains(42, 'a')).toBe(false); + expect(contains("string", 'a')).toBe(false); +}); From cd77ea5b1fe9d0f91ef73493a7e06f0620e65e44 Mon Sep 17 00:00:00 2001 From: enjoy15 Date: Fri, 31 Oct 2025 20:44:54 +0000 Subject: [PATCH 06/14] update lookup --- Sprint-2/implement/lookup.js | 13 +++++++++++-- Sprint-2/implement/lookup.test.js | 17 ++++++++++++++++- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/Sprint-2/implement/lookup.js b/Sprint-2/implement/lookup.js index a6746e07f..dc428475c 100644 --- a/Sprint-2/implement/lookup.js +++ b/Sprint-2/implement/lookup.js @@ -1,5 +1,14 @@ -function createLookup() { - // implementation here + +function createLookup(pairs) { + const lookup = {}; + if (!Array.isArray(pairs)) return lookup; + for (const pair of pairs) { + if (Array.isArray(pair) && pair.length === 2) { + const [country, currency] = pair; + lookup[country] = currency; + } + } + return lookup; } module.exports = createLookup; diff --git a/Sprint-2/implement/lookup.test.js b/Sprint-2/implement/lookup.test.js index 547e06c5a..86132ad24 100644 --- a/Sprint-2/implement/lookup.test.js +++ b/Sprint-2/implement/lookup.test.js @@ -1,6 +1,21 @@ 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 pairs = [['US', 'USD'], ['CA', 'CAD'], ['GB', 'GBP']]; + const expected = { US: 'USD', CA: 'CAD', GB: 'GBP' }; + expect(createLookup(pairs)).toEqual(expected); +}); + +test("returns empty object for empty input", () => { + expect(createLookup([])).toEqual({}); +}); + +test("ignores invalid pairs", () => { + const pairs = [['US', 'USD'], ['CA'], 'random', ['GB', 'GBP']]; + const expected = { US: 'USD', GB: 'GBP' }; + expect(createLookup(pairs)).toEqual(expected); +}); /* From 09284da0fcce986176eda010487cf3e0d29a8cbf Mon Sep 17 00:00:00 2001 From: enjoy15 Date: Fri, 31 Oct 2025 21:29:06 +0000 Subject: [PATCH 07/14] implement tally.test.js --- Sprint-2/implement/tally.js | 11 ++++++++++- Sprint-2/implement/tally.test.js | 24 ++++++++++++++++++++---- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index f47321812..8690954d7 100644 --- a/Sprint-2/implement/tally.js +++ b/Sprint-2/implement/tally.js @@ -1,3 +1,12 @@ -function tally() {} +function tally(items) { + if (!Array.isArray(items)) { + throw new Error("Invalid input"); + } + const counts = {}; + for (const item of items) { + counts[item] = (counts[item] || 0) + 1; + } + return counts; +} module.exports = tally; diff --git a/Sprint-2/implement/tally.test.js b/Sprint-2/implement/tally.test.js index 2ceffa8dd..bd4d3c172 100644 --- a/Sprint-2/implement/tally.test.js +++ b/Sprint-2/implement/tally.test.js @@ -19,16 +19,32 @@ 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("tally returns correct counts for unique items", () => { + const items = ['apple', 'banana', 'orange']; + const expected = { apple: 1, banana: 1, orange: 1 }; + expect(tally(items)).toEqual(expected); +}); // 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 returns correct counts for duplicate items", () => { + const items = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple']; + const expected = { apple: 3, banana: 2, orange: 1 }; + expect(tally(items)).toEqual(expected); +}); // Given an invalid input like a string // When passed to tally // Then it should throw an error +test("tally throws an error for invalid input", () => { + expect(() => tally("invalid")).toThrowError("Invalid input"); + expect(() => tally(123)).toThrowError("Invalid input"); + expect(() => tally({})).toThrowError("Invalid input"); + expect(() => tally(null)).toThrowError("Invalid input"); + expect(() => tally(undefined)).toThrowError("Invalid input"); +}); From 0745d876ee9efbdec13aa3f95b4f503c963f121e Mon Sep 17 00:00:00 2001 From: enjoy15 Date: Fri, 31 Oct 2025 21:58:20 +0000 Subject: [PATCH 08/14] implement querystring --- Sprint-2/implement/querystring.js | 13 +++++++-- Sprint-2/implement/querystring.test.js | 39 ++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 45ec4e5f3..71267a362 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -6,8 +6,17 @@ function parseQueryString(queryString) { const keyValuePairs = queryString.split("&"); for (const pair of keyValuePairs) { - const [key, value] = pair.split("="); - queryParams[key] = value; + const parts = pair.split("="); + const key = parts[0]; + + if (parts.length === 1) { + // No = found, set value to undefined + queryParams[key] = undefined; + } else { + // Join everything after the first = back together + const value = parts.slice(1).join("="); + queryParams[key] = value; + } } return queryParams; diff --git a/Sprint-2/implement/querystring.test.js b/Sprint-2/implement/querystring.test.js index 3e218b789..487344c5d 100644 --- a/Sprint-2/implement/querystring.test.js +++ b/Sprint-2/implement/querystring.test.js @@ -10,3 +10,42 @@ test("parses querystring values containing =", () => { "equation": "x=y+1", }); }); + +test("parses basic single parameter", () => { + expect(parseQueryString("name=John")).toEqual({ + "name": "John", + }); +}); + +test("parses multiple parameters", () => { + expect(parseQueryString("name=John&age=30&city=London")).toEqual({ + "name": "John", + "age": "30", + "city": "London", + }); +}); + +test("handles empty querystring", () => { + expect(parseQueryString("")).toEqual({}); +}); + +test("handles parameters with empty values", () => { + expect(parseQueryString("name=&age=30")).toEqual({ + "name": "", + "age": "30", + }); +}); + +test("handles parameters without values (no = sign)", () => { + expect(parseQueryString("flag&name=John")).toEqual({ + "flag": undefined, + "name": "John", + }); +}); + +test("handles multiple = signs in value", () => { + expect(parseQueryString("formula=a=b=c&x=1")).toEqual({ + "formula": "a=b=c", + "x": "1", + }); +}); From 5f7b5a28c2bb87b60b11abe24ef5bc6010ba76e5 Mon Sep 17 00:00:00 2001 From: enjoy15 Date: Fri, 31 Oct 2025 22:31:56 +0000 Subject: [PATCH 09/14] interpret invert.js --- Sprint-2/interpret/invert.js | 20 +++++++++++++++++++- Sprint-2/interpret/invert.test.js | 24 ++++++++++++++++++++++++ 2 files changed, 43 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..4f738b03f 100644 --- a/Sprint-2/interpret/invert.js +++ b/Sprint-2/interpret/invert.js @@ -10,20 +10,38 @@ function invert(obj) { const invertedObj = {}; for (const [key, value] of Object.entries(obj)) { - invertedObj.key = value; + invertedObj[value] = key; // Previously invertedObj.key = value; BUG HERE! Fixed: use bracket notation and swap key/value } return invertedObj; } // a) What is the current return value when invert is called with { a : 1 } +// Answer: { key: 1 } +// The bug causes it to create a property literally named "key" instead of using the variable value // b) What is the current return value when invert is called with { a: 1, b: 2 } +// Answer: { key: 2 } +// It keeps overwriting the same "key" property with each value from the loop // c) What is the target return value when invert is called with {a : 1, b: 2} +// Answer: { "1": "a", "2": "b" } +// The values should become keys, and the keys should become values // c) What does Object.entries return? Why is it needed in this program? +// Answer: Object.entries(obj) returns an array of [key, value] pairs. +// Example: Object.entries({ a: 1, b: 2 }) returns [["a", 1], ["b", 2]] +// It's needed so we can iterate through both keys AND values of the object at the same time +// Without Object.entries, we can only loop through keys OR values, not both. // d) Explain why the current return value is different from the target output +// Answer: The bug is using dot notation: invertedObj.key = value +// Dot notation creates a property literally named "key" instead of using the variable's value +// It should use bracket notation: invertedObj[value] = key +// Also, it should swap the key and value positions // e) Fix the implementation of invert (and write tests to prove it's fixed!) +// Fixed! The correct line is: invertedObj[value] = key; +// Tests have been written in invert.test.js and all pass ✓ + +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..de75ec281 --- /dev/null +++ b/Sprint-2/interpret/invert.test.js @@ -0,0 +1,24 @@ +const invert = require('./invert.js'); + +test('inverts object with single key-value pair', () => { + expect(invert({ a: 1 })).toEqual({ "1": "a" }); +}); + +test('inverts object with multiple key-value pairs', () => { + expect(invert({ a: 1, b: 2 })).toEqual({ "1": "a", "2": "b" }); +}); + +test('inverts object with string values', () => { + expect(invert({ x: 10, y: 20 })).toEqual({ "10": "x", "20": "y" }); +}); + +test('inverts object with string keys and values', () => { + expect(invert({ name: "Alice", city: "London" })).toEqual({ + "Alice": "name", + "London": "city" + }); +}); + +test('returns empty object for empty input', () => { + expect(invert({})).toEqual({}); +}); From 60de6c2b97e9b99d03666f1800b436553c511832 Mon Sep 17 00:00:00 2001 From: enjoy15 Date: Thu, 20 Nov 2025 20:26:35 +0000 Subject: [PATCH 10/14] update contains.js --- Sprint-2/implement/contains.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Sprint-2/implement/contains.js b/Sprint-2/implement/contains.js index 6420a8a14..80a47714f 100644 --- a/Sprint-2/implement/contains.js +++ b/Sprint-2/implement/contains.js @@ -4,12 +4,11 @@ function contains(obj, prop) { if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) { return false; } - for (const key in obj) { - if (key === prop) { - return true; - } - } - return false; + // Use built‑in instead of manual for..in loop + // Object.hasOwn (Node 16+) can be used; fallback to hasOwnProperty for wider support + // return Object.hasOwn ? Object.hasOwn(obj, prop) : Object.prototype.hasOwnProperty.call(obj, prop); + + return Object.prototype.hasOwnProperty.call(obj, prop); } module.exports = contains; From 8235d7e3b33f89e8e26874a216d701fcc34dfc81 Mon Sep 17 00:00:00 2001 From: enjoy15 Date: Thu, 20 Nov 2025 20:39:26 +0000 Subject: [PATCH 11/14] update contains.test.js --- Sprint-2/implement/contains.test.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index 2d239e028..8e03fd2d0 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -45,7 +45,14 @@ test("contains returns false for non-existent property", () => { // When passed to contains // Then it should return false or throw an error test("contains returns false for invalid parameters", () => { - expect(contains([], 'a')).toBe(false); + const arr = [1, 2, 3]; + arr.extra = "value"; // Add own property to prove arrays are rejected + expect(contains(arr, 'extra')).toBe(false); // Would be true if implementation wrongly treated arrays as objects + + // Also ensure indexed keys are rejected (array has '0' as own property) + expect(contains(['x'], '0')).toBe(false); + + expect(contains([], 'a')).toBe(false); expect(contains(null, 'a')).toBe(false); expect(contains(undefined, 'a')).toBe(false); expect(contains(42, 'a')).toBe(false); From 9ee9ec15ff145273f8075d63cd5a1291c1e18a27 Mon Sep 17 00:00:00 2001 From: enjoy15 Date: Thu, 20 Nov 2025 20:46:29 +0000 Subject: [PATCH 12/14] update querystring.js --- Sprint-2/implement/querystring.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 71267a362..3b1f20798 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -7,15 +7,17 @@ function parseQueryString(queryString) { for (const pair of keyValuePairs) { const parts = pair.split("="); - const key = parts[0]; - + const decode = (s) => decodeURIComponent(s.replace(/\+/g, "%20")); + const rawKey = parts[0]; + const key = decode(rawKey); + if (parts.length === 1) { - // No = found, set value to undefined + // No '=' present → undefined value queryParams[key] = undefined; } else { - // Join everything after the first = back together - const value = parts.slice(1).join("="); - queryParams[key] = value; + // Rejoin remaining pieces (handles '=' inside value), then decode + const rawValue = parts.slice(1).join("="); + queryParams[key] = decode(rawValue); } } From 66fa6b60987ade7b686ff9260c795e5df3d08f64 Mon Sep 17 00:00:00 2001 From: enjoy15 Date: Thu, 20 Nov 2025 20:49:40 +0000 Subject: [PATCH 13/14] update tally.js --- Sprint-2/implement/tally.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index 8690954d7..e88d55663 100644 --- a/Sprint-2/implement/tally.js +++ b/Sprint-2/implement/tally.js @@ -2,10 +2,11 @@ function tally(items) { if (!Array.isArray(items)) { throw new Error("Invalid input"); } - const counts = {}; - for (const item of items) { - counts[item] = (counts[item] || 0) + 1; - } + // Use object with no prototype to avoid collisions (e.g. "toString") + const counts = Object.create(null); + for (const item of items) { + counts[item] = (counts[item] || 0) + 1; + } return counts; } From cc3849325fd07a54f4ff79db29e59b6066539476 Mon Sep 17 00:00:00 2001 From: enjoy15 Date: Thu, 20 Nov 2025 20:52:03 +0000 Subject: [PATCH 14/14] update invert.test.js --- Sprint-2/interpret/invert.test.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Sprint-2/interpret/invert.test.js b/Sprint-2/interpret/invert.test.js index de75ec281..84ce9fc55 100644 --- a/Sprint-2/interpret/invert.test.js +++ b/Sprint-2/interpret/invert.test.js @@ -4,12 +4,9 @@ test('inverts object with single key-value pair', () => { expect(invert({ a: 1 })).toEqual({ "1": "a" }); }); -test('inverts object with multiple key-value pairs', () => { - expect(invert({ a: 1, b: 2 })).toEqual({ "1": "a", "2": "b" }); -}); - -test('inverts object with string values', () => { - expect(invert({ x: 10, y: 20 })).toEqual({ "10": "x", "20": "y" }); +// Combined test for numeric and string values +test('inverts object with multiple key-value pairs (numeric and string values)', () => { + expect(invert({ a: 1, b: 2, x: 10, y: 20 })).toEqual({ "1": "a", "2": "b", "10": "x", "20": "y" }); }); test('inverts object with string keys and values', () => {