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
4 changes: 2 additions & 2 deletions Sprint-2/debug/address.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Predict and explain first...

// this code should log out undefined because even the object "address" is well defined but the property "houseNumber" is not being correctly accessed in the last line of code.
// 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 +12,4 @@ const address = {
postcode: "XYZ 123",
};

console.log(`My house number is ${address[0]}`);
console.log(`My house number is ${address.houseNumber}`);
6 changes: 3 additions & 3 deletions Sprint-2/debug/author.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Predict and explain first...

// it should log out an error because the for statement is not correctly expressed and the prorties of the object are accessed instead of the 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 +11,6 @@ const author = {
alive: true,
};

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

// it should not log out the ingredients property's values because the ingredients are stored in an array and the code is not correctly accessing the array elements.
// 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 +12,4 @@ const recipe = {

console.log(`${recipe.title} serves ${recipe.serves}
ingredients:
${recipe}`);
${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(obj, name) {
if (typeof obj !== "object" || obj === null || Array.isArray(obj)) {
return false;
}
return Object.hasOwn(obj, name);
}

module.exports = contains;
22 changes: 21 additions & 1 deletion Sprint-2/implement/contains.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,36 @@ 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, returns false", () => {
expect(contains({}, "x")).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 properties, returns true when passed to contains with an existing property name", () => {
expect(
contains({ gitName: "djebsoft", position: "cyf trainee" }, "gitName")
).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 with properties, returns false when passed to contains with a non-existent property name", () => {
expect(
contains({ gitName: "djebsoft", position: "cyf trainee" }, "age")
).toEqual(false);
});

// Given invalid parameters like an array
// When passed to contains
// Then it should return false or throw an error
// "length" is perfect for the test because it's real property on the array itself
test("given invalid parameters (array), returns false or throws an error", () => {
expect(contains(["gitName", "age", "position"], "length")).toBe(false);
});

test("given invalid parameters (like null or undefined), returns false or throws an error", () => {
expect(contains(null, "gitName")).toEqual(false);
});
Comment on lines 45 to +51
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This test does not yet confirm that the function correctly returns false when the first argument is an array.
This is because contains(["gitName", "position"], "gitName") could also return false simply because "gitName" is not a key of the array.

Arrays are objects, with their indices acting as keys. A proper test should use a valid
key to ensure the function returns false specifically because the input is an array, not because the key is missing.

After you fixed this test, make sure you also run the test to check your function.

Note: When testing invalid type of data, undefined and null are usually good candidates to test -- many functions fail because they could not handle undefined or null.

Copy link
Copy Markdown
Author

@djebsoft djebsoft Apr 9, 2026

Choose a reason for hiding this comment

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

thank you for the feedback
I just realised that the false returned cases should be the exception, so I changed the if condition to the other way around.
I removed the case of empty object (object length equals to zero) as it does not add any plus to the code.
I also added the case where the input is an array to return false.
and run the test to check the function.
added a test with null input for invalid type of data.

Copy link
Copy Markdown
Contributor

@cjyuan cjyuan Apr 9, 2026

Choose a reason for hiding this comment

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

Why not also update the test (the one that tests array)?

Your function is correct, but we write tests not only to verify our current implementation, but also to ensure that future changes do not alter the function's expected behavior.

Copy link
Copy Markdown
Author

@djebsoft djebsoft Apr 9, 2026

Choose a reason for hiding this comment

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

thanks for the spot
sorry that I got confused about this point
i think the perfect example test is to test tricky arguments where the first argument looks like an object by having two elements and the second argument is string that match one the elements in the first argument
therefore, if the first argument wasn't an array than the test should return true. but because it an array, the function returns false as invalid input.
could you enlighten me about what I'm mistaken here ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

expect(contains(["gitName", "position"], "gitName")).toEqual(false);

The following function (changed in the future by someone else) could also pass the above test:

function contains(obj, key) {
    if (obj == null || typeof obj != "object") return false;
    return Object.hasOwn(obj, key);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This test is supposed to fail when the function is implemented as:

function contains(obj, key) {
    if (obj == null || typeof obj != "object") return false;
    return Object.hasOwn(obj, key);
}

The new test still behaves the same as the previous one.

Arrays are objects, with their indices acting as keys. You should use a valid
key
to ensure the function returns false specifically because the input is an array, not because the key is missing.

23 changes: 21 additions & 2 deletions Sprint-2/implement/lookup.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
function createLookup() {
// implementation here
// the first element is the country code
// the second array is the currency code of the country
// the function should return an object
// the keys of the object should be the first element of each inner array (country code)
// the values of the object should be the second element of each inner array (currency code)

function createLookup(countryCurrencyPairs) {
// check if the argument is an array of arrays
if (
!Array.isArray(countryCurrencyPairs) ||
// check if each inner array is an array.
countryCurrencyPairs.some((pair) => !Array.isArray(pair)) ||
// check if each inner array has two elements.
countryCurrencyPairs.some((pair) => pair.length !== 2)
// we can also use index to check the pair is an array as follows:
// for (let i = 0; i < countryCurrencyPairs.length; i++)
// return Array.isArray(countryCurrencyPairs[i])
) {
throw new Error("Invalid input: expected an array of arrays");
}
return Object.fromEntries(countryCurrencyPairs);
}

module.exports = createLookup;
52 changes: 49 additions & 3 deletions Sprint-2/implement/lookup.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
const createLookup = require("./lookup.js");
const Lookup = 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 input = [
["DZ", "DZD"],
["CA", "CAD"],
["GB", "GBP"],
];
const result = {
DZ: "DZD",
CA: "CAD",
GB: "GBP",
};
expect(Lookup(input)).toEqual(result);
});

/*

Expand All @@ -24,7 +36,7 @@ Example
Given: [['US', 'USD'], ['CA', 'CAD']]

When
createLookup(countryCurrencyPairs) is called
Lookup(countryCurrencyPairs) is called

Then
It should return:
Expand All @@ -33,3 +45,37 @@ It should return:
'CA': 'CAD'
}
*/
// Given an invalid input (not an array of arrays),
test('given string input throw "Invalid input: expected an array of arrays"', () => {
let input = "invalid input: expected an array of arrays";
expect(() => Lookup(input)).toThrow(
"Invalid input: expected an array of arrays"
);
});

// Given an array where its elements are not arrays,
test('given array with non-array elements throw "Invalid input: expected an array of arrays"', () => {
let input = [["US", "USD"], "CA"];
expect(() => Lookup(input)).toThrow(
"Invalid input: expected an array of arrays"
);
});

// Given an array where its elements are arrays with more than two elements,
test('given arrays with too many elements throw "Invalid input: expected an array of arrays"', () => {
const input = [
["US", "USD", "flag"],
["CA", "CAD"],
];
expect(() => Lookup(input)).toThrow(
"Invalid input: expected an array of arrays"
);
});

// Given an array where its elements are arrays with less than two elements,
test('given arrays with too few elements throw "Invalid input: expected an array of arrays"', () => {
const input = [["US", "USD"], ["CA"]];
expect(() => Lookup(input)).toThrow(
"Invalid input: expected an array of arrays"
);
});
11 changes: 9 additions & 2 deletions Sprint-2/implement/querystring.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
// the bug in the code is that it does not handle cases where the query string has two = signs or more between two parameters.
// in this case we make sure to split the string only at the first = sign.
// so we use the spread operator.
function parseQueryString(queryString) {
const queryParams = {};
if (queryString === null || queryString === undefined) {
return "invalid query string";
}
if (queryString.length === 0) {
return queryParams;
}
const keyValuePairs = queryString.split("&");

for (const pair of keyValuePairs) {
const [key, value] = pair.split("=");
queryParams[key] = value;
const [key, ...value] = pair.split("=");
// we convert the value array back to a string and we get back the = signs by using it as separator.
queryParams[key] = value.join("=");
}

return queryParams;
Expand Down
79 changes: 77 additions & 2 deletions Sprint-2/implement/querystring.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,85 @@
// 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");

// testing when the query string with a value contains = signs
test("parses querystring values containing =", () => {
expect(parseQueryString("equation=x=y+1")).toEqual({
"equation": "x=y+1",
equation: "x=y+1",
});
});

// testing when the query string has multiple ampersands
test("parses query string with multiple parameters", () => {
expect(parseQueryString("name=Ben&age=45&city=new%20city")).toEqual({
name: "Ben",
age: "45",
city: "new%20city",
});
});

// testing when the query string has two ampersands in a row
test("parses query string with two ampersands in a row", () => {
expect(parseQueryString("name=Ben&&age=45")).toEqual({
name: "Ben",
"": "",
age: "45",
});
});

// testing when the query string has no ampersands
test("parses query string with no ampersands", () => {
expect(parseQueryString("name=Ben")).toEqual({
name: "Ben",
});
});

// testing when the query string has no ampersands and no equal signs
test("parses query string with no ampersands and no equal signs", () => {
expect(parseQueryString("name")).toEqual({
name: "",
});
});

// testing when the query string is empty
test("parses query string with empty value", () => {
expect(parseQueryString("")).toEqual({});
});

// testing when the query string is null
test("parses query string with null value", () => {
expect(parseQueryString(null)).toEqual("invalid query string");
});

// testing when the query string is undefined
test("parses query string with undefined value", () => {
expect(parseQueryString(undefined)).toEqual("invalid query string");
});

// testing when a pair ends with equal sign (no value)
test("parses query string with no equal sign", () => {
expect(parseQueryString("name=Ben&age=&city=")).toEqual({
name: "Ben",
age: "",
city: "",
});
});

// testing when a pair starts with equal sign (no key)
test("parses query string starting with equal sign", () => {
expect(parseQueryString("name=Ben&=45")).toEqual({
name: "Ben",
"": "45",
});
});

// testing when a pair has no equal sign (no value and no key)
// the pair will be the key and the value will be an empty string
test("parses query string with no equal sign", () => {
expect(parseQueryString("name&age=45&city")).toEqual({
name: "",
age: "45",
city: "",
});
});
14 changes: 12 additions & 2 deletions Sprint-2/implement/tally.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
function tally() {}

function tally(arr) {
if (!Array.isArray(arr)) {
throw new Error("invalid input");
}
// using Object.create(null) to create a plain object without prototype
let result = Object.create(null);
for (let i = 0; i <= arr.length - 1; i++) {
let item = arr[i];
result[item] = (result[item] || 0) + 1;
}
return result;
}
module.exports = tally;
50 changes: 49 additions & 1 deletion Sprint-2/implement/tally.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,60 @@ 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");
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 on an array with duplicates returns correct counts for each unique item", () => {
expect(
tally([
"CYF",
"CYF",
"AWS",
"Capgemini",
"Deloitte",
"Google",
"Slack",
"Capgemini",
])
).toEqual({ CYF: 2, AWS: 1, Capgemini: 2, Deloitte: 1, Google: 1, Slack: 1 });
});

test("tally on an array with duplicates returns correct counts for each unique item", () => {
expect(tally(["toString", "toString"])).toEqual({ toString: 2 });
});

// Given an input that you mentioned in the review.
//test("tally on an array with duplicates returns correct counts for each unique item", () => {
// expect(tally(["toString", "toString"])).toEqual({ toString: 2 });
//});

// Given an invalid input like a string
// When passed to tally
// Then it should throw an error
test("given invalid input throws an error", () => {
expect(() => tally("invalid")).toThrow("invalid input");
});

// Given an invalid input like a number
test("given invalid input throws an error", () => {
expect(() => tally(123)).toThrow("invalid input");
});

// Given an invalid input like an object
test("given invalid input throws an error", () => {
expect(() => tally({})).toThrow("invalid input");
});

// Given an invalid input like null
test("given invalid input throws an error", () => {
expect(() => tally(null)).toThrow("invalid input");
});

// Given an invalid input like undefined
test("given invalid input throws an error", () => {
expect(() => tally(undefined)).toThrow("invalid input");
});
Loading
Loading