Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
Expand Up @@ -12,4 +12,9 @@ const address = {
postcode: "XYZ 123",
};

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

// The original syntax works for array and not for object.
// it will show undefine message
// Instead use the correct . operator to access the object field values
12 changes: 10 additions & 2 deletions Sprint-2/debug/author.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ const author = {
alive: true,
};

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

for (const key in author) {
console.log(author[key]);
}

// the iteration method does not work for the object.
// TypeError TypeError: author is not iterable will be shown
// Change the correct method with the object
14 changes: 11 additions & 3 deletions Sprint-2/debug/recipe.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ const recipe = {
ingredients: ["olive oil", "tomatoes", "salt", "pepper"],
};

console.log(`${recipe.title} serves ${recipe.serves}
ingredients:
${recipe}`);
//console.log(`${recipe.title} serves ${recipe.serves}
// ingredients:
//${recipe}`);

for (const ingredient of recipe.ingredients) {
console.log(ingredient);
}


// [object Object] will be shown because JavaScript converts an object to a string, it becomes "[object object].
// Use the syntax to access the ingredients directly
12 changes: 11 additions & 1 deletion Sprint-2/implement/contains.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
function contains() {}
function contains() {

// Return false if obj is null, undefined, an array, or not a non-null object
if (typeof obj !== "object" || obj === null || Array.isArray(obj)) {
return false;
}

// Check if the property exists directly on the object
return Object.hasOwn(obj, prop);

}

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

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

// Given an object with properties
// When passed to contains with a non-existent property name
// Then it should return false

// Given invalid parameters like an array
// When passed to contains
// Then it should return false or throw an error
// Given an empty object
// When passed to contains
// Then it should return 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("returns true when passed an existing property name", () => {
const inputObj = { a: 1, b: 2 };
expect(contains(inputObj, "a")).toBe(true);
expect(contains(inputObj, "b")).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 passed a non-existent property name", () => {
const inputObj = { a: 1, b: 2 };
expect(contains(inputObj, "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 arrays or primitives", () => {
expect(contains([1, 2, 3], "0")).toBe(false);
});
6 changes: 5 additions & 1 deletion Sprint-2/implement/lookup.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
function createLookup() {
function createLookup(countryCurrencyPairs) {

return Object.fromEntries(countryCurrencyPairs);


// implementation here
}

Expand Down
51 changes: 18 additions & 33 deletions Sprint-2/implement/lookup.test.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,20 @@
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

Acceptance Criteria:

Given
- An array of arrays representing country code and currency code pairs
e.g. [['US', 'USD'], ['CA', 'CAD']]

When
- createLookup function is called with the country-currency array as an argument

Then
- It should return an object where:
- The keys are the country codes
- The values are the corresponding currency codes

Example
Given: [['US', 'USD'], ['CA', 'CAD']]

When
createLookup(countryCurrencyPairs) is called

Then
It should return:
{
'US': 'USD',
'CA': 'CAD'
}
*/
test("creates a country currency code lookup for multiple codes", () => {
// Given
const input = [
["US", "USD"],
["CA", "CAD"],
];

const expectedOutput = {
US: "USD",
CA: "CAD",
};

// When
const result = createLookup(input);

// Then
expect(result).toEqual(expectedOutput);
});
43 changes: 40 additions & 3 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) {

if (!queryString || queryString.length === 0) {
return queryParams;
}

// Helper to decode '+' as spaces and percent-encoded characters
function decodeParam(str) {
return decodeURIComponent(str.replace(/\+/g, " "));
}

// Split by '&' to get raw key-value pairs
const keyValuePairs = queryString.split("&");

for (const pair of keyValuePairs) {
const [key, value] = pair.split("=");
queryParams[key] = value;
// Ignore empty pairs caused by trailing or duplicate '&' (e.g. "a=1&&b=2&")
if (pair.length === 0) {
continue;
}

let rawKey, rawValue;
const equalIndex = pair.indexOf("=");

if (equalIndex === -1) {
// Key with no '=' (e.g., "key") -> value is empty string
rawKey = pair;
rawValue = "";
} else {
// Split on the FIRST '=' only (handles values containing '=', e.g. "a=b-2")
rawKey = pair.slice(0, equalIndex);
rawValue = pair.slice(equalIndex + 1);
}

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

// Stretch Goal: Handle duplicate keys by converting to an array
if (Object.prototype.hasOwnProperty.call(queryParams, key)) {
if (Array.isArray(queryParams[key])) {
queryParams[key].push(value);
} else {
queryParams[key] = [queryParams[key], value];
}
} else {
queryParams[key] = value;
}
}

return queryParams;
Expand Down
6 changes: 6 additions & 0 deletions Sprint-2/implement/querystring.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ test("should replace '+' by ' '", () => {
});

// Stretch exercise: Handling query strings that contain identical keys
test("should handle multiple duplicate keys alongside single keys", () => {
expect(parseQueryString("tag=js&tag=node&author=CYF")).toEqual({
tag: ["js", "node"],
author: "CYF",
});
});

// Delete this test if you are not working on this optional case
test("should store values of a key in an array when the key has 2 or more values", () => {
Expand Down
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(items) {
if (!Array.isArray(items)) {
throw new TypeError("Expected an array as input");
}

return items.reduce((acc, item) => {
if (Object.hasOwn(acc, item)) {
acc[item] += 1;
} else {
acc[item] = 1;
}
return acc;
}, {});
Comment thread
cywong-dev marked this conversation as resolved.
}

module.exports = tally;
15 changes: 14 additions & 1 deletion Sprint-2/implement/tally.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,29 @@ 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("returns counts for each unique item", () => {
expect(tally(["a"])).toEqual({ a: 1 });
expect(tally(["a", "b", "c"])).toEqual({ a: 1, b: 1, c: 1 });
});

// 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("returns counts for each unique item", () => {
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 an invalid input like a string", () => {
expect(() => tally("string")).toThrow("Expected an array as input");
});
Comment thread
cywong-dev marked this conversation as resolved.
26 changes: 21 additions & 5 deletions Sprint-2/interpret/invert.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,29 @@ function invert(obj) {
}

// 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}

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

Object.entries(obj) returns an array of key-value pairs as two-element arrays.
Object.entries({ a: 1, b: 2 }) returns [["a", 1], ["b", 2]].
// d) Explain why the current return value is different from the target output

The line invertedObj.key = value; contains bugs:
Comment thread
cywong-dev marked this conversation as resolved.
// e) Fix the implementation of invert (and write tests to prove it's fixed!)
function invert(obj) {
if (typeof obj !== "object" || obj === null || Array.isArray(obj)) {
throw new TypeError("Expected a plain object");
}

const invertedObj = {};

for (const [key, value] of Object.entries(obj)) {
invertedObj[value] = key;
}

return invertedObj;
}

module.exports = invert;
24 changes: 24 additions & 0 deletions Sprint-2/stretch/count-words.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,27 @@

3. Order the results to find out which word is the most common in the input
*/

function countWords(str) {
if (typeof str !== "string") {
throw new TypeError("Expected a string as input");
}

// Handle empty or whitespace-only strings
if (str.trim() === "") return {};

const words = str.split(" ");
const counts = {};

for (const word of words) {
if (counts[word]) {
counts[word] += 1;
} else {
counts[word] = 1;
}
}
Comment thread
cywong-dev marked this conversation as resolved.

return counts;
}

module.exports = countWords;
Loading