Skip to content
3 changes: 2 additions & 1 deletion Sprint-2/debug/address.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -12,4 +13,4 @@ const address = {
postcode: "XYZ 123",
};

console.log(`My house number is ${address[0]}`);
console.log(`My house number is ${address["houseNumber"]}`);
14 changes: 11 additions & 3 deletions Sprint-2/debug/author.js
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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]);
}
5 changes: 2 additions & 3 deletions Sprint-2/debug/recipe.js
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -12,4 +11,4 @@ const recipe = {

console.log(`${recipe.title} serves ${recipe.serves}
ingredients:
${recipe}`);
${recipe.ingredients.join("\n")}`);
14 changes: 13 additions & 1 deletion Sprint-2/implement/contains.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
function contains() {}


function contains(obj, prop) {
if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) {
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;

27 changes: 26 additions & 1 deletion Sprint-2/implement/contains.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,44 @@ 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", () => {
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);
expect(contains("string", 'a')).toBe(false);
});
13 changes: 11 additions & 2 deletions Sprint-2/implement/lookup.js
Original file line number Diff line number Diff line change
@@ -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;
17 changes: 16 additions & 1 deletion Sprint-2/implement/lookup.test.js
Original file line number Diff line number Diff line change
@@ -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);
});

/*

Expand Down
15 changes: 13 additions & 2 deletions Sprint-2/implement/querystring.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,19 @@ 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 decode = (s) => decodeURIComponent(s.replace(/\+/g, "%20"));
const rawKey = parts[0];
const key = decode(rawKey);

if (parts.length === 1) {
// No '=' present → undefined value
queryParams[key] = undefined;
} else {
// Rejoin remaining pieces (handles '=' inside value), then decode
const rawValue = parts.slice(1).join("=");
queryParams[key] = decode(rawValue);
}
}

return queryParams;
Expand Down
39 changes: 39 additions & 0 deletions Sprint-2/implement/querystring.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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",
});
});
12 changes: 11 additions & 1 deletion Sprint-2/implement/tally.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
function tally() {}
function tally(items) {
if (!Array.isArray(items)) {
throw new Error("Invalid input");
}
// 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;
}

module.exports = tally;
24 changes: 20 additions & 4 deletions Sprint-2/implement/tally.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
});
20 changes: 19 additions & 1 deletion Sprint-2/interpret/invert.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
21 changes: 21 additions & 0 deletions Sprint-2/interpret/invert.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const invert = require('./invert.js');

test('inverts object with single key-value pair', () => {
expect(invert({ a: 1 })).toEqual({ "1": "a" });
});

// 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', () => {
expect(invert({ name: "Alice", city: "London" })).toEqual({
"Alice": "name",
"London": "city"
});
});

test('returns empty object for empty input', () => {
expect(invert({})).toEqual({});
});