Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
13acaf4
Address.js log function corrected
Mahtem Nov 25, 2025
7f8b224
Author.js corrected.
Mahtem Nov 25, 2025
ba4ef98
recipe.js log function corrected.
Mahtem Nov 25, 2025
6453fe4
contains.js function implemented and tested.
Mahtem Nov 25, 2025
cb4642b
In contains.test.js tests for contains.test.js passed
Mahtem Nov 25, 2025
9dd1769
In lookup.js function createLookup implemented.
Mahtem Nov 26, 2025
8bdd6c5
In lookup.test.js test cases added and passed.
Mahtem Nov 26, 2025
2c03056
In querystring.js function implemented.
Mahtem Nov 27, 2025
1101ce6
In querystring.test.js test edge cases added.
Mahtem Nov 27, 2025
d0665bd
In tally.js function tally() implemented.
Mahtem Nov 27, 2025
d471bca
In tally.test.js test cases added and passed.
Mahtem Nov 27, 2025
e550d17
In invert.js invert function implemented.
Mahtem Dec 4, 2025
218caa6
// In invert.test.js test cases tested.
Mahtem Dec 4, 2025
d2985e4
In count-words.js function countWords implemented.
Mahtem Dec 4, 2025
6dff138
In mode.js function calculateMode refactored.
Mahtem Dec 4, 2025
0088e75
In mode.test.js test cases passed
Mahtem Dec 4, 2025
bd20fa1
In till.js function implemented.
Mahtem Dec 6, 2025
3735e91
In till.test.js test cases written and tested.
Mahtem Dec 6, 2025
2f7f0e2
recipe.js modified "${recipe.ingredients.join("\n")}`) to make ingre…
Mahtem Dec 10, 2025
161b871
In contains.js modified return function as return Object.hasOwn(obj, …
Mahtem Dec 10, 2025
5cddc23
contains.test.js re-tested.
Mahtem Dec 10, 2025
aab642f
Decoding of paramKey = decodeURIComponent(pair); added.
Mahtem Dec 10, 2025
7ef1f9c
In tally.js modification made.
Mahtem Dec 10, 2025
ba1bc62
Modified to handle inherent property cases and to remove empty spaces.
Mahtem Dec 10, 2025
2528d55
The "if (typeof obj !== "object" || obj === null)" modified.
Mahtem Dec 10, 2025
8e4807e
Test case description updated
Mahtem Dec 10, 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
10 changes: 9 additions & 1 deletion Sprint-2/debug/address.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
// Predict and explain first...

// address is an object, so we can't access it by address[0] like in array we need to use a notation
// address.houseNumber, address.street, address.city etc ...

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

console.log(`My house number is ${address[0]}`);

console.log(`My house number is ${address.houseNumber}`);



// Address.js log function corrected
8 changes: 7 additions & 1 deletion Sprint-2/debug/author.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
// Predict and explain first...

// I thought the value in the log needed to get attached with the author like value.author
// But the error was author is not iterable as it is an object, so we need to modify the for ... of loop as
// for(const value of Object.values(author))

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

for (const value of author) {
for (const value of Object.values(author)) {
console.log(value);
}

// Author.js corrected.
9 changes: 8 additions & 1 deletion Sprint-2/debug/recipe.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,11 @@ const recipe = {

console.log(`${recipe.title} serves ${recipe.serves}
ingredients:
${recipe}`);
${recipe.ingredients.join("\n")}`);


// I guess, because the values of ingredients are given as array,
// the way ingredients is passed to the log is not right.

// recipe.js log function corrected.
// recipe.js modified "${recipe.ingredients.join("\n")}`) to make ingredients appear on new line.
18 changes: 17 additions & 1 deletion Sprint-2/implement/contains.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
function contains() {}
function contains(obj, key) {
if (typeof obj !== "object" || obj === null) {
return false;
}
//return key in obj;
return Object.hasOwn(obj, key);

}

module.exports = contains;

// In contains.js function implemented and tested.

let obj = {}, propertyName = "toString";
console.log( propertyName in obj ); // true
console.log( Object.hasOwn(obj, propertyName) ); // false

// In contains.js modified return function as return Object.hasOwn(obj, key);
// The "if (typeof obj !== "object" || obj === null)" modified.
37 changes: 36 additions & 1 deletion Sprint-2/implement/contains.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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

test("Should return true when key exists in object", () => {
const obj = { a: 1, b: 2 };
expect(contains(obj, "a")).toEqual(true);
});

// Given an empty object
// When passed to contains
// Then it should return false
test.todo("contains on empty object returns false");
//test.todo("contains on empty object returns false");

test("Should return false when empty object is passed", () => {
const obj = {}; // empty object
expect(contains(obj, "a")).toEqual(false);
})

// Given an object with properties
// When passed to contains with an existing property name
// Then it should return true

test("Should return true when object has the property", () => {
const obj = { a: 5, c: 6, r: 5 };
expect(contains(obj, "c")).toEqual(true);
});
// Given an object with properties
// When passed to contains with a non-existent property name
// Then it should return false

test("Should return false when object does not have the property", () => {
const obj = { a: 5, c: 6, r: 5 };
expect(contains(obj, "z")).toEqual(false); // non-existent property name
});
// Given invalid parameters like an array
// When passed to contains
// Then it should return false or throw an error

test("Should correctly detect keys in arrays", () => {
expect(contains([1, 2, 3], "1")).toEqual(true); // "1" is a key
expect(contains([1, 2, 3], "3")).toEqual(false); // "3" is not a key
});

test("Should return false when invalid parameters are used", () => {
expect(contains(null, "a")).toEqual(false); // null
expect(contains(5, "a")).toEqual(false); // number
expect(contains("hello", "a")).toEqual(false); // string
})

// In contains.test.js tests for contains.test.js passed

// contains.test.js re-tested.

// test case description differentiated.
26 changes: 24 additions & 2 deletions Sprint-2/implement/lookup.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
function createLookup() {
// implementation here
function createLookup(input) {
if (!Array.isArray(input)) {
throw new Error("Input must be an array of [country, currency] pairs");
}

const lookup = {};

for (const pair of input) {
if (!Array.isArray(pair) || pair.length !== 2) {
throw new Error("Each item must be an array of [country, currency]");
}

const [country, currency] = pair;

if (typeof country !== "string" || typeof currency !== "string") {
throw new Error("Each item must be an array of [country, currency]");
}

lookup[country] = currency;
}

return lookup;
}

module.exports = createLookup;

// In lookup.js function createLookup implemented.
49 changes: 48 additions & 1 deletion Sprint-2/implement/lookup.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const createLookup = require("./lookup.js");

test.todo("creates a country currency code lookup for multiple codes");
//test.todo("creates a country currency code lookup for multiple codes");

/*

Expand Down Expand Up @@ -33,3 +33,50 @@ It should return:
'CA': 'CAD'
}
*/
// Case 1: Valid input

test("returns an object with country codes as keys and currency codes as values", () => {
const input = [
["US", "USD"],
["CA", "CAD"],
];
const expected = { US: "USD", CA: "CAD" };

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

// Case 2: Empty array

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

// Case 3: Input not an array

test("throws an error when input is not an array", () => {
expect(() => createLookup("not an array")).toThrow(
"Input must be an array of [country, currency] pairs"
);
});

// Case 4: Input null or undefined

test("throws an error when input is null or undefined", () => {
expect(() => createLookup(null)).toThrow(
"Input must be an array of [country, currency] pairs"
);
expect(() => createLookup(undefined)).toThrow(
"Input must be an array of [country, currency] pairs"
);
});

// Case 5: type of Input elements are not strings

test("throws an error if inner array elements are not valid strings", () => {
const invalidInput = [['US', 'USD'], ['CA', 123], ['JP', null]];
expect(() => createLookup(invalidInput)).toThrow(
"Each item must be an array of [country, currency]"
);
});

// In lookup.test.js test cases added and passed.
44 changes: 37 additions & 7 deletions Sprint-2/implement/querystring.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,46 @@
function parseQueryString(queryString) {
const queryParams = {};
const parsedParams = {}; // Gets the final key-value pairs

if (!queryString) return parsedParams;

// Removes leading '?' if present

if (queryString.startsWith("?")) {
queryString = queryString.slice(1);
}

if (queryString.length === 0) {
return queryParams;
return parsedParams;
}
const keyValuePairs = queryString.split("&");

for (const pair of keyValuePairs) {
const [key, value] = pair.split("=");
queryParams[key] = value;
// Split the string into individual key-value pairs
const pairs = queryString.split("&");

for (const pair of pairs) {
if (!pair) continue; // skip empty segments (like from && or trailing &) eg "name=John&&age=30"

const equalSignIndex = pair.indexOf("=");

let paramKey, paramValue;

if (equalSignIndex === -1) {

// If '=' not found we have a key exists but value is empty

paramKey = decodeURIComponent(pair);
paramValue = "";
} else {
paramKey = decodeURIComponent(pair.slice(0, equalSignIndex));
paramValue = decodeURIComponent(pair.slice(equalSignIndex + 1));
}

parsedParams[paramKey] = paramValue; // overwrite previous value if key repeats
}

return queryParams;
return parsedParams;
}

module.exports = parseQueryString;

// In querystring.js function implemented.
// Decoding of paramKey = decodeURIComponent(pair); added.
67 changes: 67 additions & 0 deletions Sprint-2/implement/querystring.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,70 @@ test("parses querystring values containing =", () => {
"equation": "x=y+1",
});
});

// Given an empty query string

test("Empty querystring returns empty object", () => {
expect(parseQueryString("")).toEqual({});
});

// A single key with value not given

test("Single Key with No Value", () => {
expect(parseQueryString("key=")).toEqual({ key: "" });
});

// A key without"=" sign

test("Key Without = Sign", () => {
expect(parseQueryString("KeyAlone")).toEqual({ KeyAlone: "" });
});

// Multiple parameters given

test("Multiple Parameters", () => {
expect(parseQueryString("name=Smith&age=40&job=Teacher")).toEqual({
name: "Smith",
age: "40",
job: "Teacher",
});
});

// If values contain special characters

test("Values Containing Special Characters", () => {
expect(parseQueryString("query=a%20b%26c%3Dd")).toEqual({ query: "a b&c=d" });
});

// Query starting with "?"

test("Starting with ?", () => {
expect(parseQueryString("?foo=bar")).toEqual({ foo: "bar" });
});

// Given encoded key eg, %20 to " "

test("Encoded keys, decoded correctly", () => {
expect(parseQueryString("first%20name=John")).toEqual({
"first name": "John",
});
});

// Mixed encoded

test("Mixed encoded and plain parts", () => {
expect(parseQueryString("message=Hello%20World%21&Ans=Hello")).toEqual({
message: "Hello World!",
Ans: "Hello",
});
});

// Ignore repeated symbols --- &&
test("Skips any empty parts that appear because of repeated symbols", () => {
expect(parseQueryString("a=1&&b=2")).toEqual({
a: "1",
b: "2",
});
});

// In querystring.test.js test edge cases added.
28 changes: 27 additions & 1 deletion Sprint-2/implement/tally.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,29 @@
function tally() {}
function tally(items) {
if (!Array.isArray(items)) {
throw new Error("Input must be an array");
}

if (items.length === 0) {
return {};
}

const counts = Object.create(null);

for (const item of items) {

// Convert objects and arrays to JSON string
const key = (typeof item === "object" && item !== null) ? JSON.stringify(item) : item;
counts[key] = (counts[key] || 0) + 1;
}

return counts;
}

console.log(tally(["toStrin", "toStrin"]));
console.log(tally(["toString", "toString"]));

module.exports = tally;

// In tally.js function tally() implemented.

// modification made.
Loading