Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0e5bd64
fix(debug): use dot notation for address.houseNumber access
Tarawally Nov 26, 2025
420e298
fix: resolve TypeError by using Object.values() to iterate over autho…
Tarawally Nov 26, 2025
13ef0b9
fix: correct ingredient logging in recipe.js to display each ingredie…
Tarawally Nov 26, 2025
5bd937e
feat: implement and test contains function for object property checking
Tarawally Nov 27, 2025
ad67bd7
fix: use dot notation to access object property
Tarawally Nov 29, 2025
b5500ee
fix: use Object.values() to iterate over object
Tarawally Nov 29, 2025
8470b22
fix: access properties directly and join array for output
Tarawally Nov 29, 2025
a1d45f7
feat: implement contains function to check object properties
Tarawally Nov 29, 2025
160eb23
test: add tests for contains function
Tarawally Nov 29, 2025
a32e08b
feat: implement createLookup function for key-value pairs
Tarawally Nov 29, 2025
22585d3
test: add tests for createLookup function
Tarawally Nov 29, 2025
3812df8
test: add tests for parseQueryString function
Tarawally Nov 29, 2025
fb98fcb
feat: implement parseQueryString with indexOf for edge cases
Tarawally Nov 29, 2025
1862d49
test: add tests for tally function
Tarawally Nov 29, 2025
bd8a3dc
feat: implement tally function to count array items
Tarawally Nov 29, 2025
071d46c
fix: use bracket notation to correctly invert object keys and values
Tarawally Nov 29, 2025
7881920
feat(alarmclock): implement countdown timer with setInterval
Tarawally Nov 29, 2025
f3adf96
fix(alarmclock): update page title to 'Alarm clock app'
Tarawally Nov 29, 2025
fbc7f3f
feat(quote-generator): implement random quote display with auto-play
Tarawally Nov 29, 2025
69034a9
fix(quote-generator): update page title, heading and add stylesheet
Tarawally Nov 29, 2025
b9495db
style(quote-generator): add CSS for centered layout and typography
Tarawally Nov 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion Sprint-2/debug/address.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
// Predict and explain first...

// 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...
// Fix anything that isn't working
Expand All @@ -12,4 +17,5 @@ const address = {
postcode: "XYZ 123",
};

console.log(`My house number is ${address[0]}`);
// Fix: Dot notation accesses the property correctly
console.log(`My house number is ${address.houseNumber}`);
8 changes: 7 additions & 1 deletion Sprint-2/debug/author.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
// Predict and explain first...

// 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
// - 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

Expand All @@ -11,6 +16,7 @@ const author = {
alive: true,
};

for (const value of author) {
// Fix: Object.values() returns an array that can be looped over
for (const value of Object.values(author)) {
console.log(value);
}
9 changes: 8 additions & 1 deletion Sprint-2/debug/recipe.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
// Predict and explain first...

// 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?
Expand All @@ -10,6 +16,7 @@ const recipe = {
ingredients: ["olive oil", "tomatoes", "salt", "pepper"],
};

// Fix: Accesses each property directly and uses join() to format the ingredients
console.log(`${recipe.title} serves ${recipe.serves}
ingredients:
${recipe}`);
${recipe.ingredients.join("\n")}`);
15 changes: 14 additions & 1 deletion Sprint-2/implement/contains.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
function contains() {}
// 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;
}

return obj.hasOwnProperty(propertyName);
}

module.exports = contains;
41 changes: 38 additions & 3 deletions Sprint-2/implement/contains.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -17,19 +17,54 @@ 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

// Returns true when the property exists
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");

// An empty object has no properties
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

// 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);
});

// 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

// 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);
});

// 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);
});

// 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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Arrays are objects in JavaScript, and they do have property names -- just not the same ones as objects.
Which keys do arrays have, and how does that affect how reliable your test is?

When testing whether the function handles arrays properly, try using a key that an array might
realistically contain
. Otherwise, you might get a passing test even if the function isn't checking for arrays at all.

expect(contains(null, "a")).toBe(false);
expect(contains(undefined, "a")).toBe(false);
});
19 changes: 17 additions & 2 deletions Sprint-2/implement/lookup.js
Original file line number Diff line number Diff line change
@@ -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];
Comment on lines +9 to +11
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could also consider using array destructuring to simplify this 3 lines of code.


// Bracket notation allows using a variable as the property name
lookupObject[key] = value;
}

return lookupObject;
}

module.exports = createLookup;
17 changes: 15 additions & 2 deletions Sprint-2/implement/lookup.test.js
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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);
});
16 changes: 15 additions & 1 deletion Sprint-2/implement/querystring.js
Original file line number Diff line number Diff line change
@@ -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);
Comment on lines +19 to +22
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the query string is "key1&key2" (i.e., without =)?


queryParams[key] = value;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In real query string, both key and value are percent-encoded or URL encoded.
For example,

tags%5B%5D=hello%20world -> key is tags[], value is hello world

Can your function handle URL-encoded query string?

Suggestion: Look up "How to decode a URL-encoded string in JavaScript".

}

Expand Down
48 changes: 46 additions & 2 deletions Sprint-2/implement/querystring.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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",
});
});
29 changes: 28 additions & 1 deletion Sprint-2/implement/tally.js
Original file line number Diff line number Diff line change
@@ -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 = {};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the following function call returns the value you expect?

  tally(["toString", "toString"]);

Suggestion: Look up an approach to create an empty object with no inherited properties.


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;
63 changes: 61 additions & 2 deletions Sprint-2/implement/tally.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
});
Loading