Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 6 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...

/* Prediction - the house number would return as undefined.

Explaination - "address" is a plain javascript object, not an array, arrays make use of the numeric index positions like it was used in
the initial code but objects use key value pairs (properties) */

// 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,4 @@ const address = {
postcode: "XYZ 123",
};

console.log(`My house number is ${address[0]}`);
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,11 @@
// Predict and explain first...

/* Prediction - TypeError: author is not iterable.

Explaination - The "for... of" loop is designed fir iterable objects like arrays, strings, maps or sets,
which have built in protocols. plain javascript objects like "author" are not iterable by default,
which would cause the error when the "for... of" loop is used. */

// 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 +17,6 @@ const author = {
alive: true,
};

for (const value of author) {
for (const value of Object.Values(author)) {
console.log(value);
}
11 changes: 8 additions & 3 deletions Sprint-2/debug/recipe.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
// Predict and explain first...

/* PREDICTION - it would print out the name, how many it serves perfectly but wouldn't work well for the ingredients.

EXPLANATION
Printing the whole object instead of ingredients: The template literal uses ${recipe} at the end. When JavaScript tries to insert an object into a template literal string,
it calls .toString() on the object, which results in the generic string "[object Object]" instead of showing its properties or the array inside. */

// 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,5 @@ const recipe = {
ingredients: ["olive oil", "tomatoes", "salt", "pepper"],
};

console.log(`${recipe.title} serves ${recipe.serves}
ingredients:
${recipe}`);
console.log(`${recipe.title} serves ${recipe.serves}\n
ingredients:${recipe.ingredients.join("\n")}`);
7 changes: 6 additions & 1 deletion Sprint-2/implement/contains.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
function contains() {}
function contains() {
if (typeof obj !== "object" || obj === null || Array.isArray(obj)) {
return false;
}
return Object.prototype.hasOwnProperty.call(obj, prop);
}

module.exports = contains;
20 changes: 19 additions & 1 deletion Sprint-2/implement/contains.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,34 @@ 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");

describe("contains()", () => {
test("returns false when given 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("returns true when the object contains the property name", () => {
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 the object does not contain the property name", () => {
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 when passed invalid parameters like an array or primitive", () => {
expect(contains(["a", "b"], "0")).toBe(false);
expect(contains(null, "a")).toBe(false);
expect(contains("string", "length")).toBe(false);
});
});
5 changes: 4 additions & 1 deletion Sprint-2/implement/lookup.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
function createLookup() {
// implementation here
if (!Array.isArray(countryCurrencyPairs)) {
return {};
}
return Object.fromEntries(countryCurrencyPairs);
}

module.exports = createLookup;
26 changes: 25 additions & 1 deletion Sprint-2/implement/lookup.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,30 @@
const createLookup = require("./lookup.js");

test.todo("creates a country currency code lookup for multiple codes");
describe("createLookup()", () => {
test("creates a country currency code lookup for multiple codes", () => {
const input = [
["US", "USD"],
["CA", "CAD"],
["GB", "GBP"],
];
const expected = {
US: "USD",
CA: "CAD",
GB: "GBP",
};

expect(createLookup(input)).toEqual(expected);
});

test("returns an empty object when passed an empty array", () => {
expect(createLookup([])).toEqual({});
});

test("returns an empty object when passed invalid inputs", () => {
expect(createLookup(null)).toEqual({});
expect(createLookup("invalid")).toEqual({});
});
});

/*

Expand Down
65 changes: 57 additions & 8 deletions Sprint-2/implement/querystring.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,65 @@
function parseQueryString(queryString) {
const queryParams = {};
if (queryString.length === 0) {
return queryParams;
const result = {};

// Return empty object for empty or non-string inputs
if (typeof queryString !== "string" || !queryString.trim()) {
return result;
}
const keyValuePairs = queryString.split("&");

for (const pair of keyValuePairs) {
const [key, value] = pair.split("=");
queryParams[key] = value;
// Optional: strip leading '?' if passed a full URL search string (e.g., "?a=1")
const sanitizedInput = queryString.startsWith("?")
? queryString.slice(1)
: queryString;

// Split pairs by '&'
const pairs = sanitizedInput.split("&");

for (const pair of pairs) {
// Ignore empty pairs (e.g. from consecutive '&&' or trailing '&')
if (!pair) continue;

// Find the FIRST '=' to separate key and value correctly
const equalIndex = pair.indexOf("=");

let rawKey = "";
let rawValue = "";

if (equalIndex === -1) {
// Key with no '=' (e.g., "key" -> key="key", value="")
rawKey = pair;
rawValue = "";
} else {
// Split strictly on the first '=' so values containing '=' stay intact
rawKey = pair.slice(0, equalIndex);
rawValue = pair.slice(equalIndex + 1);
}

// Helper to decode query string encoding (+ to space, percent-encoding)
const decodeParam = (str) => {
try {
return decodeURIComponent(str.replace(/\+/g, " "));
} catch {
// Fallback if URI decoding fails on malformed input
return str.replace(/\+/g, " ");
}
};

const key = decodeParam(rawKey);
const value = decodeParam(rawValue);

// Handle single key vs duplicate keys
if (Object.prototype.hasOwnProperty.call(result, key)) {
if (Array.isArray(result[key])) {
result[key].push(value);
} else {
result[key] = [result[key], value];
}
} else {
result[key] = value;
}
}

return queryParams;
return result;
}

module.exports = parseQueryString;
20 changes: 19 additions & 1 deletion Sprint-2/implement/querystring.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// Below are some test cases the implementation doesn't handle well.
// Fix the implementation for these tests, 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");

test("should parse values containing '='", () => {
expect(parseQueryString("equation=a=b-2")).toEqual({
Expand Down Expand Up @@ -46,3 +46,21 @@ test("should store values of a key in an array when the key has 2 or more values
foo: "bar",
});
});
test("should strip leading '?' if present", () => {
expect(parseQueryString("?foo=bar&baz=qux")).toEqual({
foo: "bar",
baz: "qux",
});
});

test("should handle empty or invalid inputs gracefully", () => {
expect(parseQueryString("")).toEqual({});
expect(parseQueryString(null)).toEqual({});
expect(parseQueryString(undefined)).toEqual({});
});

test("should gracefully handle malformed percent encoding", () => {
expect(parseQueryString("key=%E0%A4")).toEqual({
key: "%E0%A4",
});
});
11 changes: 10 additions & 1 deletion Sprint-2/implement/tally.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
function tally() {}
function tally() {
if (!Array.isArray(items)) {
throw new TypeError("Input must be an array");
}
return items.reduce((acc, item) => {
// Increment count if key exists, otherwise initialize to 1
acc[item] = (acc[item] || 0) + 1;
return acc;
}, {});
}

module.exports = tally;
31 changes: 24 additions & 7 deletions Sprint-2/implement/tally.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,29 @@ const tally = require("./tally.js");
// 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");

// Given an array with duplicate items
// When passed to tally
// Then it should return counts for each unique item
describe("tally()", () => {
test("tally on an empty array returns an empty object", () => {
expect(tally([])).toEqual({});
});

// Given an invalid input like a string
// When passed to tally
// Then it should throw an error
// Given an array with duplicate items
// When passed to tally
// Then it should return counts for each unique item

test("returns counts for single and duplicate items", () => {
expect(tally(["a"])).toEqual({ a: 1 });
expect(tally(["a", "a", "a"])).toEqual({ a: 3 });
expect(tally(["a", "a", "b", "c"])).toEqual({ a: 2, b: 1, c: 1 });
});

// Given an invalid input like a string
// When passed to tally
// Then it should throw an error

test("throws an error when passed invalid input like a string or null", () => {
expect(() => tally("a string")).toThrow(TypeError);
expect(() => tally(123)).toThrow(TypeError);
expect(() => tally(null)).toThrow(TypeError);
});
});
45 changes: 41 additions & 4 deletions Sprint-2/interpret/invert.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// Then it should swap the keys and values in the object

// E.g. invert({x : 10, y : 20}), target output: {"10": "x", "20": "y"}
/*

function invert(obj) {
const invertedObj = {};
Expand All @@ -15,15 +16,51 @@ function invert(obj) {

return invertedObj;
}
*/
/* a) What is the current return value when invert is called with { a : 1 }

// a) What is the current return value when invert is called with { a : 1 }
{ key: 1 }

// b) What is the current return value when invert is called with { a: 1, b: 2 }

{ key: 2 }
// c) What is the target return value when invert is called with {a : 1, b: 2}

// c) What does Object.entries return? Why is it needed in this program?
{ "1": "a", "2": "b" }
// d) What does Object.entries return? Why is it needed in this program?

// d) Explain why the current return value is different from the target output
Object.entries(obj) returns an array of an object's own key-value pairs as [key, value] tuples (e.g., Object.entries({ a: 1, b: 2 }) returns [['a', 1], ['b', 2]]).

// e) Fix the implementation of invert (and write tests to prove it's fixed!)
It is needed here so we can loop over both the property name (key) and its corresponding value (value) at the same time using array destructuring (const [key, value]).

// e) Explain why the current return value is different from the target output

There are two critical issues in the current loop
1. Static property assignment instead of dynamic lookup;
The code writes invertedObj.key = value. Using dot notation literally creates/overwrites a single key named "key" on the object every single iteration.
To use the variable's value dynamically as the object key, you must use bracket notation: invertedObj[value].

2. Flipped key and value;
The target output asks to swap keys and values (so values become keys and keys become values). The original code assigns value to the object's key position,
rather than assigning key to the position indexed by value (invertedObj[value] = key). */

// f) Fix the implementation of invert (and write tests to prove it's fixed!)

function invert(obj) {
// Guard clause for non-object inputs
if (typeof obj !== "object" || obj === null || Array.isArray(obj)) {
return {};
}

const invertedObj = {};

for (const [key, value] of Object.entries(obj)) {
// Use bracket notation to dynamically set the property name to 'value'
// and assign 'key' as its value
invertedObj[value] = key;
}

return invertedObj;
}

module.exports = invert;
22 changes: 22 additions & 0 deletions Sprint-2/interpret/invert.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const invert = require("./invert.js");

describe("invert()", () => {
test("swaps keys and values for a single key-value pair", () => {
expect(invert({ a: 1 })).toEqual({ 1: "a" });
});

test("swaps keys and values for multiple key-value pairs", () => {
expect(invert({ x: 10, y: 20 })).toEqual({ 10: "x", 20: "y" });
expect(invert({ a: "1", b: "2" })).toEqual({ 1: "a", 2: "b" });
});

test("handles empty objects", () => {
expect(invert({})).toEqual({});
});

test("handles non-object invalid inputs gracefully", () => {
expect(invert(null)).toEqual({});
expect(invert(["a", "b"])).toEqual({});
expect(invert("string")).toEqual({});
});
});
Loading