From 0e5bd642d35c5e7aeddc546d3c8be1f0665f8f7f Mon Sep 17 00:00:00 2001 From: Tarawally <41825140+Tarawally@users.noreply.github.com> Date: Wed, 26 Nov 2025 11:32:31 +0000 Subject: [PATCH 01/21] fix(debug): use dot notation for address.houseNumber access --- Sprint-2/debug/address.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Sprint-2/debug/address.js b/Sprint-2/debug/address.js index 940a6af83..eb3a443e4 100644 --- a/Sprint-2/debug/address.js +++ b/Sprint-2/debug/address.js @@ -1,5 +1,12 @@ // Predict and explain first... +// The code will likely output undefined. +// This is because `address[0]` tries to access an object property using an array index. +// However, `address` is an object, not an array. Objects use keys (property names) to access values. +// References: +// - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors#dot_notation +// - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors#bracket_notation + // This code should log out the houseNumber from the address object // but it isn't working... // Fix anything that isn't working @@ -12,4 +19,8 @@ const address = { postcode: "XYZ 123", }; -console.log(`My house number is ${address[0]}`); +// Access the property using dot notation or bracket notation with the key name: +// - Dot notation: `address.houseNumber` +// - Bracket notation: `address["houseNumber"]` + +console.log(`My house number is ${address.houseNumber}`); From 420e2983d7de5859959fc6fdb63f043300648d52 Mon Sep 17 00:00:00 2001 From: Tarawally <41825140+Tarawally@users.noreply.github.com> Date: Wed, 26 Nov 2025 22:43:54 +0000 Subject: [PATCH 02/21] fix: resolve TypeError by using Object.values() to iterate over author object properties --- Sprint-2/debug/author.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Sprint-2/debug/author.js b/Sprint-2/debug/author.js index 8c2125977..aacfca41e 100644 --- a/Sprint-2/debug/author.js +++ b/Sprint-2/debug/author.js @@ -1,5 +1,11 @@ // Predict and explain first... +// The code will throw a `TypeError` because `author` is a plain object, and plain objects are not iterable (cannot be used with `for...of`). +// References: +// - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of#iterating_over_plain_objects +// - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/values + + // 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,7 @@ const author = { alive: true, }; -for (const value of author) { +// Get an array of values to iterate over +for (const value of Object.values(author)) { console.log(value); } From 13ef0b997e5aac7d05846d0a9f570f4fe7e08386 Mon Sep 17 00:00:00 2001 From: Tarawally <41825140+Tarawally@users.noreply.github.com> Date: Wed, 26 Nov 2025 23:03:52 +0000 Subject: [PATCH 03/21] fix: correct ingredient logging in recipe.js to display each ingredient on a new line --- Sprint-2/debug/recipe.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Sprint-2/debug/recipe.js b/Sprint-2/debug/recipe.js index 6cbdd22cd..0e2e5573c 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -1,5 +1,13 @@ // Predict and explain first... +// The output will contain `[object Object]` instead of the ingredients list. +// Interpolating an object `${recipe}` calls its `toString()` method. +// The default `toString()` for objects returns `"[object Object]"`. +// References: +// - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString +// - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join + + // 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 +18,8 @@ const recipe = { ingredients: ["olive oil", "tomatoes", "salt", "pepper"], }; + +// `join("\n")` converts the array into a single string with newlines between. console.log(`${recipe.title} serves ${recipe.serves} ingredients: -${recipe}`); +${recipe.ingredients.join("\n")}`); From 5bd937e950c0bc610e2c7e9beff0f9671636f214 Mon Sep 17 00:00:00 2001 From: Tarawally <41825140+Tarawally@users.noreply.github.com> Date: Thu, 27 Nov 2025 00:29:04 +0000 Subject: [PATCH 04/21] feat: implement and test contains function for object property checking --- Sprint-2/implement/contains.js | 8 +++++++- Sprint-2/implement/contains.test.js | 29 ++++++++++++++++++++++++++--- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/Sprint-2/implement/contains.js b/Sprint-2/implement/contains.js index cd779308a..922f9a383 100644 --- a/Sprint-2/implement/contains.js +++ b/Sprint-2/implement/contains.js @@ -1,3 +1,9 @@ -function contains() {} +function contains(obj, propertyName) { + if (typeof obj !== "object" || obj === null || Array.isArray(obj)) { + return false; + } + + return obj.hasOwnProperty(propertyName); +} module.exports = contains; diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index 326bdb1f2..440e5441e 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -8,7 +8,7 @@ E.g. contains({a: 1, b: 2}, 'a') // returns true as the object contains a key of 'a' E.g. contains({a: 1, b: 2}, 'c') // returns false -as the object doesn't contains a key of 'c' +as the object doesn't contain a key of 'c' */ // Acceptance criteria: @@ -16,20 +16,43 @@ 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("contains returns true if the property exists", () => { + expect(contains({ a: 1, b: 2 }, "a")).toBe(true); +}); // Given an empty object // When passed to contains // Then it should return false -test.todo("contains on empty object returns false"); +test("contains returns false for 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("contains returns true for another property that exists", () => { + expect(contains({ name: "Lisa", age: 73 }, "name")).toBe(true); +}); // Given an object with properties -// When passed to contains with a non-existent property name +// When passed to contains with a property name that does not exist // Then it should return false +test("contains returns false for a property that does not exist", () => { + expect(contains({ name: "Leo", age: 19 }, "height")).toBe(false); +}); + +// Given an object +// When passed to contains with a property that does not exist +// Then it should return false +test("contains returns false when the property is not present", () => { + 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("contains handles invalid parameters", () => { + expect(contains([], "a")).toBe(false); + expect(contains(null, "a")).toBe(false); + expect(contains(undefined, "a")).toBe(false); +}); From ad67bd746fbf88d9094056516cc4e1e2c1d6d691 Mon Sep 17 00:00:00 2001 From: Tarawally <41825140+Tarawally@users.noreply.github.com> Date: Sat, 29 Nov 2025 00:31:57 +0000 Subject: [PATCH 05/21] fix: use dot notation to access object property --- Sprint-2/debug/address.js | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/Sprint-2/debug/address.js b/Sprint-2/debug/address.js index eb3a443e4..66b62aecc 100644 --- a/Sprint-2/debug/address.js +++ b/Sprint-2/debug/address.js @@ -1,11 +1,9 @@ // Predict and explain first... -// The code will likely output undefined. -// This is because `address[0]` tries to access an object property using an array index. -// However, `address` is an object, not an array. Objects use keys (property names) to access values. -// References: -// - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors#dot_notation -// - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors#bracket_notation +// This outputs undefined because address[0] tries to access an object like an array. +// Objects use property names, not numeric indices. +// References: +// - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors // This code should log out the houseNumber from the address object // but it isn't working... @@ -19,8 +17,5 @@ const address = { postcode: "XYZ 123", }; -// Access the property using dot notation or bracket notation with the key name: -// - Dot notation: `address.houseNumber` -// - Bracket notation: `address["houseNumber"]` - +// Fix: Dot notation accesses the property correctly console.log(`My house number is ${address.houseNumber}`); From b5500ee45fc47f88c3d1250d2f4ba602eb7ade5c Mon Sep 17 00:00:00 2001 From: Tarawally <41825140+Tarawally@users.noreply.github.com> Date: Sat, 29 Nov 2025 00:36:01 +0000 Subject: [PATCH 06/21] fix: use Object.values() to iterate over object --- Sprint-2/debug/author.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Sprint-2/debug/author.js b/Sprint-2/debug/author.js index aacfca41e..acf633799 100644 --- a/Sprint-2/debug/author.js +++ b/Sprint-2/debug/author.js @@ -1,11 +1,10 @@ // Predict and explain first... -// The code will throw a `TypeError` because `author` is a plain object, and plain objects are not iterable (cannot be used with `for...of`). +// This throws a TypeError because plain objects cannot be used with for...of directly. // References: -// - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of#iterating_over_plain_objects +// - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of // - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/values - // 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 @@ -17,7 +16,7 @@ const author = { alive: true, }; -// Get an array of values to iterate over +// Fix: Object.values() returns an array that can be looped over for (const value of Object.values(author)) { console.log(value); } From 8470b2242ea56d6ce1def8bbe8835e99afd1574c Mon Sep 17 00:00:00 2001 From: Tarawally <41825140+Tarawally@users.noreply.github.com> Date: Sat, 29 Nov 2025 00:36:47 +0000 Subject: [PATCH 07/21] fix: access properties directly and join array for output --- Sprint-2/debug/recipe.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Sprint-2/debug/recipe.js b/Sprint-2/debug/recipe.js index 0e2e5573c..06de93d0d 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -1,13 +1,11 @@ // Predict and explain first... -// The output will contain `[object Object]` instead of the ingredients list. -// Interpolating an object `${recipe}` calls its `toString()` method. -// The default `toString()` for objects returns `"[object Object]"`. +// This outputs [object Object] because putting an object in a template string calls toString(). +// The default toString() for objects just returns "[object Object]". // References: // - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString // - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join - // 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? @@ -18,8 +16,7 @@ const recipe = { ingredients: ["olive oil", "tomatoes", "salt", "pepper"], }; - -// `join("\n")` converts the array into a single string with newlines between. +// Fix: Accesses each property directly and uses join() to format the ingredients console.log(`${recipe.title} serves ${recipe.serves} ingredients: ${recipe.ingredients.join("\n")}`); From a1d45f7ad6d753f877cee416e5300abc919e94f8 Mon Sep 17 00:00:00 2001 From: Tarawally <41825140+Tarawally@users.noreply.github.com> Date: Sat, 29 Nov 2025 00:38:42 +0000 Subject: [PATCH 08/21] feat: implement contains function to check object properties --- Sprint-2/implement/contains.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Sprint-2/implement/contains.js b/Sprint-2/implement/contains.js index 922f9a383..6c0e188ca 100644 --- a/Sprint-2/implement/contains.js +++ b/Sprint-2/implement/contains.js @@ -1,4 +1,11 @@ +// Checks if an object has a specific property. +// References: +// - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty +// - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof +// - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray + function contains(obj, propertyName) { + // Returns false for invalid inputs (null, arrays, or non-objects) if (typeof obj !== "object" || obj === null || Array.isArray(obj)) { return false; } From 160eb23916fa13404b01d8f70fec8d6c53c4dafa Mon Sep 17 00:00:00 2001 From: Tarawally <41825140+Tarawally@users.noreply.github.com> Date: Sat, 29 Nov 2025 00:39:26 +0000 Subject: [PATCH 09/21] test: add tests for contains function --- Sprint-2/implement/contains.test.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index 440e5441e..90b5e0a8a 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -16,6 +16,8 @@ as the object doesn't contain 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 + +// Returns true when the property exists test("contains returns true if the property exists", () => { expect(contains({ a: 1, b: 2 }, "a")).toBe(true); }); @@ -23,6 +25,8 @@ test("contains returns true if the property exists", () => { // Given an empty object // When passed to contains // Then it should return false + +// An empty object has no properties test("contains returns false for an empty object", () => { expect(contains({}, "a")).toBe(false); }); @@ -30,6 +34,8 @@ test("contains returns false for an empty object", () => { // Given an object with properties // When passed to contains with an existing property name // Then it should return true + +// Finds a named property in a typical object test("contains returns true for another property that exists", () => { expect(contains({ name: "Lisa", age: 73 }, "name")).toBe(true); }); @@ -37,6 +43,8 @@ test("contains returns true for another property that exists", () => { // Given an object with properties // When passed to contains with a property name that does not exist // Then it should return false + +// Returns false for a missing property test("contains returns false for a property that does not exist", () => { expect(contains({ name: "Leo", age: 19 }, "height")).toBe(false); }); @@ -44,6 +52,8 @@ test("contains returns false for a property that does not exist", () => { // Given an object // When passed to contains with a property that does not exist // Then it should return false + +// Returns false for a property not in the object test("contains returns false when the property is not present", () => { expect(contains({ a: 1, b: 2 }, "c")).toBe(false); }); @@ -51,6 +61,8 @@ test("contains returns false when the property is not present", () => { // Given invalid parameters like an array // When passed to contains // Then it should return false or throw an error + +// Handles invalid inputs gracefully test("contains handles invalid parameters", () => { expect(contains([], "a")).toBe(false); expect(contains(null, "a")).toBe(false); From a32e08bcb395f7a8cc8c7f0900593e25e54f9371 Mon Sep 17 00:00:00 2001 From: Tarawally <41825140+Tarawally@users.noreply.github.com> Date: Sat, 29 Nov 2025 00:40:26 +0000 Subject: [PATCH 10/21] feat: implement createLookup function for key-value pairs --- Sprint-2/implement/lookup.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/Sprint-2/implement/lookup.js b/Sprint-2/implement/lookup.js index a6746e07f..fbef830f3 100644 --- a/Sprint-2/implement/lookup.js +++ b/Sprint-2/implement/lookup.js @@ -1,5 +1,20 @@ -function createLookup() { - // implementation here +// Converts an array of pairs into a lookup object. +// References: +// - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of +// - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors#bracket_notation + +function createLookup(arrayOfPairs) { + const lookupObject = {}; + + for (const pair of arrayOfPairs) { + const key = pair[0]; + const value = pair[1]; + + // Bracket notation allows using a variable as the property name + lookupObject[key] = value; + } + + return lookupObject; } module.exports = createLookup; From 22585d30e68052e6cc63328ea360db0c92f19b95 Mon Sep 17 00:00:00 2001 From: Tarawally <41825140+Tarawally@users.noreply.github.com> Date: Sat, 29 Nov 2025 00:40:55 +0000 Subject: [PATCH 11/21] test: add tests for createLookup function --- Sprint-2/implement/lookup.test.js | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Sprint-2/implement/lookup.test.js b/Sprint-2/implement/lookup.test.js index 547e06c5a..2ca0a2a92 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 @@ -33,3 +31,18 @@ It should return: 'CA': 'CAD' } */ + +// Converts multiple pairs into a lookup object +test("creates a country currency code lookup for multiple codes", () => { + const input = [ + ["US", "USD"], + ["CA", "CAD"], + ]; + + const expectedOutput = { + US: "USD", + CA: "CAD", + }; + + expect(createLookup(input)).toEqual(expectedOutput); +}); From 3812df85cc1cb01e1e87fd142e5f7590b3c84111 Mon Sep 17 00:00:00 2001 From: Tarawally <41825140+Tarawally@users.noreply.github.com> Date: Sat, 29 Nov 2025 00:54:24 +0000 Subject: [PATCH 12/21] test: add tests for parseQueryString function --- Sprint-2/implement/querystring.test.js | 48 ++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/Sprint-2/implement/querystring.test.js b/Sprint-2/implement/querystring.test.js index 3e218b789..6b2c0fa51 100644 --- a/Sprint-2/implement/querystring.test.js +++ b/Sprint-2/implement/querystring.test.js @@ -3,10 +3,54 @@ // 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"); +// Handles values containing "=" test("parses querystring values containing =", () => { expect(parseQueryString("equation=x=y+1")).toEqual({ - "equation": "x=y+1", + equation: "x=y+1", + }); +}); + +// Parses a simple key-value pair +test("parses a simple key-value pair", () => { + expect(parseQueryString("name=John")).toEqual({ + name: "Samantha", + }); +}); + +// Parses multiple pairs separated by "&" +test("parses multiple key-value pairs", () => { + expect(parseQueryString("name=John&age=25")).toEqual({ + name: "Samantha", + age: "33", + }); +}); + +// Returns an empty object for an empty string +test("returns an empty object for an empty string", () => { + expect(parseQueryString("")).toEqual({}); +}); + +// Handles keys with empty values +test("parses a key with no value after the equals sign", () => { + expect(parseQueryString("name=")).toEqual({ + name: "", + }); +}); + +// Preserves multiple "=" in values +test("parses querystring with multiple = signs in value", () => { + expect(parseQueryString("formula=a=b=c")).toEqual({ + formula: "a=b=c", + }); +}); + +// Handles a mix of simple and complex values +test("parses mixed simple and complex key-value pairs", () => { + expect(parseQueryString("name=John&equation=1+1=2&city=London")).toEqual({ + name: "Samantha", + equation: "1+1=2", + city: "London", }); }); From fb98fcba8a0836a09c6ac57777242dc4dc41931a Mon Sep 17 00:00:00 2001 From: Tarawally <41825140+Tarawally@users.noreply.github.com> Date: Sat, 29 Nov 2025 00:55:40 +0000 Subject: [PATCH 13/21] feat: implement parseQueryString with indexOf for edge cases --- Sprint-2/implement/querystring.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 45ec4e5f3..3c8aaf6ce 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -1,12 +1,26 @@ +// Parses a query string into an object. +// References: +// - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split +// - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf +// - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice + function parseQueryString(queryString) { const queryParams = {}; + + // Returns early if the string is empty if (queryString.length === 0) { return queryParams; } + const keyValuePairs = queryString.split("&"); for (const pair of keyValuePairs) { - const [key, value] = pair.split("="); + // Finds only the first "=" since values may contain "=" too + const firstEqualsIndex = pair.indexOf("="); + + const key = pair.slice(0, firstEqualsIndex); + const value = pair.slice(firstEqualsIndex + 1); + queryParams[key] = value; } From 1862d498341a257a715e950d68b0e0aee858e7a0 Mon Sep 17 00:00:00 2001 From: Tarawally <41825140+Tarawally@users.noreply.github.com> Date: Sat, 29 Nov 2025 00:56:40 +0000 Subject: [PATCH 14/21] test: add tests for tally function --- Sprint-2/implement/tally.test.js | 63 +++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/Sprint-2/implement/tally.test.js b/Sprint-2/implement/tally.test.js index 2ceffa8dd..fd4affadd 100644 --- a/Sprint-2/implement/tally.test.js +++ b/Sprint-2/implement/tally.test.js @@ -20,15 +20,74 @@ const tally = require("./tally.js"); // When passed an array of items // Then it should return an object containing the count for each unique item +// Counts a single item +test("tally counts a single item in an array", () => { + const input = ["a"]; + + const expectedOutput = { a: 1 }; + + expect(tally(input)).toEqual(expectedOutput); +}); + +// Counts repeated items correctly +test("tally counts multiple occurrences of the same item", () => { + const input = ["a", "a", "a"]; + + const expectedOutput = { a: 3 }; + + expect(tally(input)).toEqual(expectedOutput); +}); + +// Counts different items with varying frequencies +test("tally counts different items with varying frequencies", () => { + const input = ["a", "a", "b", "c"]; + + const expectedOutput = { a: 2, b: 1, c: 1 }; + + expect(tally(input)).toEqual(expectedOutput); +}); + // 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"); + +// Returns an empty object for an empty array +test("tally on an empty array returns an empty object", () => { + const input = []; + + const expectedOutput = {}; + + expect(tally(input)).toEqual(expectedOutput); +}); // Given an array with duplicate items // When passed to tally // Then it should return counts for each unique item -// Given an invalid input like a string +// Handles real-world data with duplicates +test("tally handles an array with many duplicate items", () => { + const input = ["apple", "banana", "apple", "orange", "banana", "apple"]; + + const expectedOutput = { apple: 3, banana: 2, orange: 1 }; + + expect(tally(input)).toEqual(expectedOutput); +}); + +// Given invalid input like a string // When passed to tally // Then it should throw an error + +// Throws an error for a string input +test("tally throws an error when given a string instead of an array", () => { + expect(() => tally("not an array")).toThrow("Input must be an array"); +}); + +// Throws an error for null +test("tally throws an error when given null", () => { + expect(() => tally(null)).toThrow("Input must be an array"); +}); + +// Throws an error for undefined +test("tally throws an error when given undefined", () => { + expect(() => tally(undefined)).toThrow("Input must be an array"); +}); From bd8a3dc41878ae2c94066ce1192b7be363998c27 Mon Sep 17 00:00:00 2001 From: Tarawally <41825140+Tarawally@users.noreply.github.com> Date: Sat, 29 Nov 2025 00:57:07 +0000 Subject: [PATCH 15/21] feat: implement tally function to count array items --- Sprint-2/implement/tally.js | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index f47321812..a5a869dcd 100644 --- a/Sprint-2/implement/tally.js +++ b/Sprint-2/implement/tally.js @@ -1,3 +1,30 @@ -function tally() {} +// Counts how often each item appears in an array. +// References: +// - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray +// - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of + +function tally(arrayOfItems) { + // Checks the input is actually an array + const inputIsNotAnArray = !Array.isArray(arrayOfItems); + + if (inputIsNotAnArray) { + throw new Error("Input must be an array"); + } + + const countObject = {}; + + for (const item of arrayOfItems) { + // If the item is already counted, add 1; otherwise start at 1 + const itemAlreadyCounted = countObject[item] !== undefined; + + if (itemAlreadyCounted) { + countObject[item] = countObject[item] + 1; + } else { + countObject[item] = 1; + } + } + + return countObject; +} module.exports = tally; From 071d46c6fb0abd3b3665a8b3c72cde49b4211fb9 Mon Sep 17 00:00:00 2001 From: Tarawally <41825140+Tarawally@users.noreply.github.com> Date: Sat, 29 Nov 2025 01:23:18 +0000 Subject: [PATCH 16/21] fix: use bracket notation to correctly invert object keys and values --- Sprint-2/interpret/invert.js | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/Sprint-2/interpret/invert.js b/Sprint-2/interpret/invert.js index bb353fb1f..97b8bf749 100644 --- a/Sprint-2/interpret/invert.js +++ b/Sprint-2/interpret/invert.js @@ -6,24 +6,42 @@ // E.g. invert({x : 10, y : 20}), target output: {"10": "x", "20": "y"} -function invert(obj) { - const invertedObj = {}; - - for (const [key, value] of Object.entries(obj)) { - invertedObj.key = value; - } - - return invertedObj; -} +// Swaps the keys and values of an object. +// References: +// - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries +// - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors#bracket_notation // a) What is the current return value when invert is called with { a : 1 } +// { key: 1 } — the literal string "key" is used as the property name. // b) What is the current return value when invert is called with { a: 1, b: 2 } +// { key: 2 } — each iteration overwrites the same "key" property. // c) What is the target return value when invert is called with {a : 1, b: 2} +// { "1": "a", "2": "b" } — values become keys and keys become values. // c) What does Object.entries return? Why is it needed in this program? +// Object.entries returns an array of [key, value] pairs. +// It is needed because for...of cannot iterate over plain objects directly. // d) Explain why the current return value is different from the target output +// The bug uses dot notation (invertedObj.key), which creates a literal property called "key". +// Bracket notation (invertedObj[value]) uses the variable's value as the property name instead. // 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)) { + // Bracket notation allows using the value as a property name + invertedObj[value] = key; + } + + return invertedObj; +} + +// Testing the fix with console.log +console.log(invert({ a: 1 })); // Expected: { "1": "a" } +console.log(invert({ a: 1, b: 2 })); // Expected: { "1": "a", "2": "b" } +console.log(invert({ x: 10, y: 20 })); // Expected: { "10": "x", "20": "y" } From 7881920cddbf1e7dae662f25fd6ebc8b3952500b Mon Sep 17 00:00:00 2001 From: Tarawally <41825140+Tarawally@users.noreply.github.com> Date: Sat, 29 Nov 2025 07:57:23 +0000 Subject: [PATCH 17/21] feat(alarmclock): implement countdown timer with setInterval --- Sprint-3/alarmclock/alarmclock.js | 52 ++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/Sprint-3/alarmclock/alarmclock.js b/Sprint-3/alarmclock/alarmclock.js index 6ca81cd3b..a971aca5a 100644 --- a/Sprint-3/alarmclock/alarmclock.js +++ b/Sprint-3/alarmclock/alarmclock.js @@ -1,4 +1,54 @@ -function setAlarm() {} +// Stores the interval ID to allow clearing the timer when needed +let intervalId; + +// Reference: https://developer.mozilla.org/en-US/docs/Web/API/setInterval +function setAlarm() { + const input = document.getElementById("alarmSet"); + let totalSeconds = parseInt(input.value); + + if (isNaN(totalSeconds) || totalSeconds < 0) { + alert("Please enter a valid positive number for the time."); + return; + } + + // Prevents multiple timers from running simultaneously + if (intervalId) clearInterval(intervalId); + + // Ensures a clean visual state before starting a new countdown + document.body.style.backgroundColor = ""; + + updateDisplay(totalSeconds); + + intervalId = setInterval(() => { + totalSeconds--; + updateDisplay(totalSeconds); + + if (totalSeconds <= 0) { + clearInterval(intervalId); + playAlarm(); + document.body.style.backgroundColor = "#db4d4d"; + } + }, 1000); +} + +function updateDisplay(totalSeconds) { + const minutes = Math.floor(totalSeconds / 60); + const seconds = totalSeconds % 60; + const formattedMinutes = String(minutes).padStart(2, "0"); + const formattedSeconds = String(seconds).padStart(2, "0"); + document.getElementById( + "timeRemaining" + ).innerText = `Time Remaining: ${formattedMinutes}:${formattedSeconds}`; +} + +// Resets the alarm state completely, allowing the user to set a new alarm +// The setup() listener handles pausing the audio separately +document.getElementById("stop").addEventListener("click", () => { + if (intervalId) clearInterval(intervalId); + document.body.style.backgroundColor = ""; + document.getElementById("timeRemaining").innerText = "Time Remaining: 00:00"; + document.getElementById("alarmSet").value = ""; +}); // DO NOT EDIT BELOW HERE From f3adf966a01d04c16770b54b5330715ab99ef3fd Mon Sep 17 00:00:00 2001 From: Tarawally <41825140+Tarawally@users.noreply.github.com> Date: Sat, 29 Nov 2025 07:57:23 +0000 Subject: [PATCH 18/21] fix(alarmclock): update page title to 'Alarm clock app' --- Sprint-3/alarmclock/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sprint-3/alarmclock/index.html b/Sprint-3/alarmclock/index.html index 48e2e80d9..ff2d3b453 100644 --- a/Sprint-3/alarmclock/index.html +++ b/Sprint-3/alarmclock/index.html @@ -4,7 +4,7 @@ -