Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
f283a3a
Fix: house number
RaihanSharif Feb 26, 2026
925a44c
Fix: author
RaihanSharif Feb 26, 2026
b65a94d
Fix: recipe
RaihanSharif Feb 26, 2026
cf033ab
Implement: contains and its tests
RaihanSharif Feb 26, 2026
e62810c
implement: lookup and tests
RaihanSharif Feb 26, 2026
41c1356
Fix: queryString
RaihanSharif Feb 26, 2026
72f6c7a
Implement: tally and tests
RaihanSharif Feb 26, 2026
79e9ce6
Implement: invert function and test cases
RaihanSharif Feb 26, 2026
ffd8465
ingredients should print on a new line
RaihanSharif Mar 7, 2026
e1a49b1
reimplement contains and add test case for array inputs
RaihanSharif Mar 7, 2026
d2c6cb5
fix lookup test for internal array length
RaihanSharif Mar 7, 2026
9b6c2ae
fix test copy paste error. Add test case for when query string is "&"…
RaihanSharif Mar 7, 2026
7bc6baa
fix tally implementation
RaihanSharif Mar 7, 2026
2ab0492
add test case for numbers only
RaihanSharif Mar 7, 2026
e5e05f6
Throw error for Date and Map types, only plain objects allowed.
RaihanSharif May 7, 2026
f3a0247
renamed for clarity
RaihanSharif May 7, 2026
761ac0e
add tests for mixed string and non-string pairs
RaihanSharif May 7, 2026
da27f88
Fix: stop deduplication on partial matching parameter value where the…
RaihanSharif May 7, 2026
9393851
remove unnecessary assignment
RaihanSharif May 7, 2026
118541d
fix: input only contains duplicates of one element
RaihanSharif May 7, 2026
5b20ef5
throw TypeError if elements of the input array are invalid types
RaihanSharif May 7, 2026
4e539bc
remove unnecessary casting of object key
RaihanSharif May 7, 2026
7f1b697
change logic so multiple keys with save value cannot be inverted
RaihanSharif May 7, 2026
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
12 changes: 11 additions & 1 deletion Sprint-2/debug/address.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
// Predict and explain first...

/*
prediction: My house number is undefined
Reason: the code is trying to access the key 0 of the address object, which
does not exist.

Object properties aren't accessible by index. Author likely tried to treat it
the same as an array. Arrays are a special type of object which allows indexing into (some)
of its values.
*/

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

console.log(`My house number is ${address[0]}`);
console.log(`My house number is ${address.houseNumber}`);
9 changes: 8 additions & 1 deletion Sprint-2/debug/author.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
// Predict and explain first...
/*
Prediction: TypeError: author is not iterable

Cause: Plain Js objects aren't iterable the way an array's elements are.
To iterate over keys and/or values use the Object.keys(), Object.values(),
or Object.entries() methods to covert the data into an array first.
*/

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

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

/*
Prediction: the ingredients: ${recipe} part will only print something like [Object object]

Cause: The code is attempting to print the recipe object itself, rather than the ingredients.
In Js, when printing objects, it doesn't print a string of key, pair values of its
properties. Should be printing the ingredients property, which is an array, which does print out its values.
*/

// 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 @@ -12,4 +20,4 @@ const recipe = {

console.log(`${recipe.title} serves ${recipe.serves}
ingredients:
${recipe}`);
${recipe.ingredients.join("\n")}`);
17 changes: 16 additions & 1 deletion Sprint-2/implement/contains.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
function contains() {}
function isObject(item) {
return (
typeof item === "object" &&
item !== null &&
!Array.isArray(item) &&
!(item instanceof Date) &&
!(item instanceof Map)
);
}

function contains(object, key) {
if (isObject(object)) {
return key in object;
}
throw new TypeError("Exepected a plain object");
}

module.exports = contains;
15 changes: 14 additions & 1 deletion Sprint-2/implement/contains.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,29 @@ 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");
test("given an empty object, should return false", () => {
expect(contains({}, "a")).toEqual(false);
});

// Given an object with properties
// When passed to contains with an existing property name
// Then it should return true
test("given an object with a property, and property name that exists, should return true", () => {
expect(contains({ a: 12, b: 2 }, "a")).toEqual(true);
});

// Given an object with properties
// When passed to contains with a non-existent property name
// Then it should return false
test("given an object, and property that does not exist in object, should return false", () => {
expect(contains({ a: 12, b: 2 }, "c")).toEqual(false);
});

// Given invalid parameters like an array
// When passed to contains
// Then it should return false or throw an error
test("given invalid parameter, like an array, should return false or throw error", () => {
expect(() => contains(22, "a")).toThrow(TypeError);
expect(() => contains([], "a")).toThrow(TypeError);
expect(() => contains(new Date(), "getFullYear")).toThrow(TypeError);
});
24 changes: 22 additions & 2 deletions Sprint-2/implement/lookup.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
function createLookup() {
// implementation here
// check if arry is 2d, and each element is a k,v pair and k,v both strings
function isValidCountryCurrencyPair(arr) {
return (
Array.isArray(arr) &&
arr.every((element) => {
return (
Array.isArray(element) &&
element.length === 2 &&
typeof element[0] === "string" &&
typeof element[1] === "string"
);
})
);
}

function createLookup(countryAndCurrency) {
// bit lazy, should have tests for type, and content seperately,
// to throw more specific errors
if (!isValidCountryCurrencyPair(countryAndCurrency)) {
throw new Error("Input is not valid type and/or format");
}
return Object.fromEntries(countryAndCurrency);
}

module.exports = createLookup;
63 changes: 61 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,64 @@ It should return:
'CA': 'CAD'
}
*/

test("converts 2d array to object", () => {
expect(
createLookup([
["a", "x"],
["b", "y"],
["c", "z"],
])
).toEqual({ a: "x", b: "y", c: "z" });
});

test("convert empty array, to empty object", () => {
expect(createLookup([])).toEqual({});
});

test("convert single 2d array, to object with one key", () => {
expect(createLookup([["a", "wow"]])).toEqual({ a: "wow" });
});

// this is the behaviour of Object.fromEntries(), I keep it for consistency
test("when there are duplicate keys, later entries overwrite earlier entries", () => {
expect(
createLookup([
["a", "ff"],
["b", "gg"],
["a", "hh"],
])
).toEqual({ b: "gg", a: "hh" });
});

test("when input is not an array throw error", () => {
expect(() => createLookup("asdf")).toThrow();
expect(() => createLookup(1)).toThrow();
expect(() => createLookup({ a: 12 })).toThrow();
});

test("when input is not a 2d array throw error", () => {
expect(() => createLookup(["a", "b"])).toThrow();
expect(() => createLookup([["a", "b"]])).not.toThrow();
});

test("when length of internal array must be exactly 2", () => {
expect(() => createLookup([["a"]])).toThrow();
expect(() => createLookup([["a", "b", "c"]])).toThrow();
});

test("when non-string k, v pairs throw error", () => {
expect(() =>
createLookup([
[1, 2],
[3, 4],
])
).toThrow();

expect(() => {
createLookup([
["a", 2],
[3, "4"],
]);
}).toThrow();
});
47 changes: 42 additions & 5 deletions Sprint-2/implement/querystring.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,50 @@
function parseQueryString(queryString) {
const queryParams = {};
if (queryString.length === 0) {
return queryParams;
}
if (!queryString || typeof queryString !== "string") return queryParams;

if (queryString.trim() === "") return queryParams;

const keyValuePairs = queryString.split("&");

for (const pair of keyValuePairs) {
const [key, value] = pair.split("=");
queryParams[key] = value;
const firstEqualsIndex = pair.indexOf("=");
let key, value;

// Checks for pairs with key but no value like debug&verbose&name=Alice
// by keeping them, prevent loss of information
if (firstEqualsIndex === -1) {
key = pair.trim();
value = "";
} else {
// splits on first equal, but not any of the rest
key = pair.slice(0, firstEqualsIndex).trim();
value = pair.slice(firstEqualsIndex + 1).trim();
}

// skips next part if the ends up being an empty str after slice and trim
if (!key) continue;

/* use hasOwnProperty to prevent conflict with built-in/inherited properties
like "toString", if the query string has a "toString=value".



if a kv pair already exists on queryParams, and value is the same as the incoming value, ignore. It's a duplicate
Otherwise, if it has a single value, change value to array containing the old value and new value.

if value at key is an array, and contains the incoming value, ignore. Else, push it to the array.
*/
if (Object.prototype.hasOwnProperty.call(queryParams, key)) {
if (!Array.isArray(queryParams[key])) {
if (queryParams[key] === value) continue;
queryParams[key] = [queryParams[key], value];
} else {
if (queryParams[key].includes(value)) continue;
queryParams[key].push(value);
}
} else {
queryParams[key] = value; // ← new key, just assign
}
}

return queryParams;
Expand Down
89 changes: 86 additions & 3 deletions Sprint-2/implement/querystring.test.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,95 @@
// In the prep, we implemented a function to parse query strings.
// Unfortunately, it contains several bugs!
// 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.
// 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");

// empty string
test("empty query string returns {}", () => {
expect(parseQueryString("")).toEqual({});
});

// null or undefined
test("null or undefined input returns {}", () => {
expect(parseQueryString(null)).toEqual({});
expect(parseQueryString(undefined)).toEqual({});
expect(parseQueryString()).toEqual({});
});

// happy path
// single valid kv pair
test("a single k, v with no special chars return object with matching k, v pair", () => {
expect(parseQueryString("mykey=myvalue")).toEqual({ mykey: "myvalue" });
});
// mutiple valid kv pairs
test("multiple k, v pairs with no special chars return object with matching k, v pairs", () => {
expect(parseQueryString("mykey=myvalue&otherkey=othervalue&third=3")).toEqual(
{
mykey: "myvalue",
otherkey: "othervalue",
third: "3",
}
);
});

// special characters in key, can't handle = in key, as that is fundamentally unparsable
test("special character in key without URL encoding", () => {
expect(parseQueryString("my+key=myvalue")).toEqual({
"my+key": "myvalue",
});
});
// special characters in value
test("parses querystring values containing =", () => {
expect(parseQueryString("equation=x=y+1")).toEqual({
"equation": "x=y+1",
equation: "x=y+1",
});
});

// missing values
test("key with no value return object with the key and value of empty string", () => {
expect(parseQueryString("foo=")).toEqual({ foo: "" });
});

//valueless flags
test("when given valueless keys, keep the key with empty string as value", () => {
expect(parseQueryString("debug&verbose")).toEqual({ debug: "", verbose: "" });
});

// ignore keyless values
test("ignores pairs with no key", () => {
expect(parseQueryString("=value")).toEqual({});
});

// duplicate keys with different values (array)
test("collects duplicate keys into an array", () => {
expect(parseQueryString("color=red&color=blue")).toEqual({
color: ["red", "blue"],
});
});

// duplicate k,v pair
test("if k,v already exists, or value array for a key already as value, ignore", () => {
expect(parseQueryString("color=red&color=red")).toEqual({ color: "red" });
});

// white spaces
test("trims whitespace from keys and values", () => {
expect(parseQueryString("name= Alice &age= 30 ")).toEqual({
name: "Alice",
age: "30",
});
});

test("string with only delimiters returns {}", () => {
expect(parseQueryString("&")).toEqual({});
expect(parseQueryString("&&")).toEqual({});
});

// substring false-positive deduplication
test("does not deduplicate a value that is a substring of an existing value", () => {
expect(parseQueryString("color=red&color=re")).toEqual({
color: ["red", "re"],
});
});
15 changes: 14 additions & 1 deletion Sprint-2/implement/tally.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
function tally() {}
function tally(list) {
if (!Array.isArray(list)) {
throw new TypeError("Invalid input: not an array");
}
return list.reduce((acc, curr) => {
if (typeof curr !== "string" && typeof curr !== "number") {
throw new TypeError(
`Invalid element: ${curr} must be a string or number`
);
}
acc[curr] = (acc[curr] ?? 0) + 1;
return acc;
}, {});
}

module.exports = tally;
Loading
Loading