Skip to content
8 changes: 5 additions & 3 deletions Sprint-2/debug/address.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// Predict and explain first...

// This code should log out the houseNumber from the address object
// but it isn't working...
// Fix anything that isn't working
// but it isn't working.
// The bug was that the code tried to access the object using index [0].
// Objects use property names instead of numeric indexes.

const address = {
houseNumber: 42,
Expand All @@ -12,4 +13,5 @@ const address = {
postcode: "XYZ 123",
};

console.log(`My house number is ${address[0]}`);
// Correctly access the houseNumber property
console.log(`My house number is ${address.houseNumber}`);
6 changes: 4 additions & 2 deletions Sprint-2/debug/author.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// Predict and explain 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
// It was not working because plain objects are not iterable with for...of.
// To loop through the values, we first convert them into an array using Object.values().

const author = {
firstName: "Zadie",
Expand All @@ -11,6 +12,7 @@ const author = {
alive: true,
};

for (const value of author) {
// Loop through all the values in the object
for (const value of Object.values(author)) {
console.log(value);
}
16 changes: 11 additions & 5 deletions Sprint-2/debug/recipe.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
// Predict and explain first...

// 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?
// Each ingredient should be logged on a new line.
// The original code tried to print the entire recipe object,
// which resulted in "[object Object]" instead of the ingredients.

const recipe = {
title: "bruschetta",
serves: 2,
ingredients: ["olive oil", "tomatoes", "salt", "pepper"],
};

console.log(`${recipe.title} serves ${recipe.serves}
ingredients:
${recipe}`);
// Print title and serving size
console.log(`${recipe.title} serves ${recipe.serves}`);
console.log("ingredients:");

// Loop through the ingredients array and print each one
for (const ingredient of recipe.ingredients) {
console.log(ingredient);
}
Comment on lines +19 to +21
Copy link
Contributor

Choose a reason for hiding this comment

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

Since ingredient values are separated by '\n' in the output, we could also use Array.prototype.join() to construct the equivalent string and then output the resulting string.

26 changes: 25 additions & 1 deletion Sprint-2/implement/contains.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,27 @@
function contains() {}
/**
* contains()
*
* Checks whether an object contains a specific property.
*
* @param {object} obj - The object to check.
* @param {string} propertyName - The property name we want to check.
* @returns {boolean} True if the property exists, otherwise false.
*/

function contains(obj, propertyName) {
// Validate that obj is actually an object
// and not null or an array
if (obj === null || typeof obj !== "object" || Array.isArray(obj)) {
return false;
}

// Validate propertyName
if (typeof propertyName !== "string" || propertyName.length === 0) {
return false;
}
Comment on lines +18 to +21
Copy link
Contributor

Choose a reason for hiding this comment

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

  • Values of non-string type can be used as property names; they are just implicitly converted to equivalent strings. For example,
let obj = {};
obj[3.0] = 12;           // 3.0 is treated as "3" when used as key
console.log(obj["3"]);   // output 12 
console.log(obj[4-1]);   // output 12 too
console.log(Object.hasOwn(obj, 3));  // output true
  • Empty string can also serve as key.


// Check if the object has the property as its own key
return Object.prototype.hasOwnProperty.call(obj, propertyName);
Copy link
Contributor

Choose a reason for hiding this comment

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

Could also use Object.hasOwn().

}

module.exports = contains;
42 changes: 19 additions & 23 deletions Sprint-2/implement/contains.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,28 @@ const contains = require("./contains.js");
Implement a function called contains that checks an object contains a
particular property

E.g. contains({a: 1, b: 2}, 'a') // returns true
as the object contains a key of 'a'

E.g. contains({a: 1, b: 2}, 'c') // returns false
as the object doesn't contains a key of 'c'
E.g. contains({a: 1, b: 2}, "a") // returns true
E.g. contains({a: 1, b: 2}, "c") // returns false
*/

// Acceptance criteria:

// Given a contains function
// When passed an object and a property name
// Then it should return true if the object contains the property, false otherwise
describe("contains()", () => {
test("returns false for an empty object", () => {
expect(contains({}, "a")).toBe(false);
});

// Given an empty object
// When passed to contains
// Then it should return false
test.todo("contains on empty object returns false");
test("returns true when the property exists", () => {
expect(contains({ a: 1, b: 2 }, "a")).toBe(true);
});

// Given an object with properties
// When passed to contains with an existing property name
// Then it should return true
test("returns false when the property does not exist", () => {
expect(contains({ a: 1, b: 2 }, "c")).toBe(false);
});

// Given an object with properties
// When passed to contains with a non-existent property name
// Then it should return false
test("returns false when given an array", () => {
expect(contains(["a", "b"], "0")).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 given null", () => {
expect(contains(null, "a")).toBe(false);
});
});
36 changes: 34 additions & 2 deletions Sprint-2/implement/lookup.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,37 @@
function createLookup() {
// implementation here
/**
* createLookup()
*
* Converts an array of [key, value] pairs into a lookup object.
*
* Example:
* [['US', 'USD'], ['CA', 'CAD']]
*
* Returns:
* { US: 'USD', CA: 'CAD' }
*/

function createLookup(pairs) {
// Ensure the input is an array
if (!Array.isArray(pairs)) {
throw new Error("Expected an array of pairs");
}

const lookup = {};

// Loop through each pair
for (const pair of pairs) {
// Validate that each pair has exactly two values
if (!Array.isArray(pair) || pair.length !== 2) {
throw new Error("Each item must be a [key, value] pair");
}

const [key, value] = pair;

// Add to lookup object
lookup[key] = value;
}

return lookup;
}

module.exports = createLookup;
65 changes: 32 additions & 33 deletions Sprint-2/implement/lookup.test.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,34 @@
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'
}
*/
describe("createLookup()", () => {
test("creates a country currency code lookup for multiple codes", () => {
const pairs = [
["US", "USD"],
["CA", "CAD"],
];

expect(createLookup(pairs)).toEqual({
US: "USD",
CA: "CAD",
});
});

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

test("overwrites duplicate keys with the last value", () => {
const pairs = [
["US", "USD"],
["US", "USN"],
];

expect(createLookup(pairs)).toEqual({
US: "USN",
});
});

test("throws an error when input is not an array", () => {
expect(() => createLookup("invalid")).toThrow();
});
});
35 changes: 33 additions & 2 deletions Sprint-2/implement/querystring.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,43 @@
/**
* parseQueryString()
*
* Parses a query string into an object of key-value pairs.
*
* Example:
* parseQueryString("name=Richard&city=Sheffield")
* returns { name: "Richard", city: "Sheffield" }
*/

function parseQueryString(queryString) {
const queryParams = {};
if (queryString.length === 0) {

// Return an empty object if the input is an empty string
if (typeof queryString !== "string" || queryString.length === 0) {
return queryParams;
}

// Split the full query string into key-value pairs
const keyValuePairs = queryString.split("&");

for (const pair of keyValuePairs) {
const [key, value] = pair.split("=");
// Skip empty pairs, for example from a trailing "&"
if (pair === "") {
continue;
}

// Find the position of the first "="
const separatorIndex = pair.indexOf("=");

// If there is no "=" sign, treat it as a key with an empty value
if (separatorIndex === -1) {
queryParams[pair] = "";
continue;
}

// Extract the key and everything after the first "=" as the value
const key = pair.slice(0, separatorIndex);
const value = pair.slice(separatorIndex + 1);

queryParams[key] = value;
Comment on lines +37 to 41
Copy link
Contributor

Choose a reason for hiding this comment

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

No change required.

Note:

  • In real query string, both key and value are percent-encoded or URL encoded in the URL.
    For example, the string "5%" is encoded as "5%25". So to get the actual value of "5%25"
    (whether it is a key or value in the query string), we need to call a function to decode it.

  • You can also explore the URLSearchParams API.

}

Expand Down
48 changes: 40 additions & 8 deletions Sprint-2/implement/querystring.test.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,44 @@
// 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.
const parseQueryString = require("./querystring.js");

const parseQueryString = require("./querystring.js")
describe("parseQueryString()", () => {
test("parses querystring values containing =", () => {
expect(parseQueryString("equation=x=y+1")).toEqual({
equation: "x=y+1",
});
});

test("returns an empty object for an empty string", () => {
expect(parseQueryString("")).toEqual({});
});

test("parses a single key-value pair", () => {
expect(parseQueryString("name=Richard")).toEqual({
name: "Richard",
});
});

test("parses multiple key-value pairs", () => {
expect(parseQueryString("name=Richard&city=Sheffield")).toEqual({
name: "Richard",
city: "Sheffield",
});
});

test("handles a key with an empty value", () => {
expect(parseQueryString("name=")).toEqual({
name: "",
});
});

test("handles a key with no equals sign", () => {
expect(parseQueryString("name")).toEqual({
name: "",
});
});

test("parses querystring values containing =", () => {
expect(parseQueryString("equation=x=y+1")).toEqual({
"equation": "x=y+1",
test("ignores an empty trailing pair", () => {
expect(parseQueryString("name=Richard&")).toEqual({
name: "Richard",
});
});
});
32 changes: 31 additions & 1 deletion Sprint-2/implement/tally.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,33 @@
function tally() {}
/**
* tally()
*
* Counts how many times each item appears in an array.
*
* Example:
* tally(['a','a','b','c'])
* returns { a: 2, b: 1, c: 1 }
*/

function tally(items) {
// Validate input
if (!Array.isArray(items)) {
throw new Error("Expected an array");
}

const counts = {};

// Loop through each item in the array
for (const item of items) {
// If the item already exists in the object, increase the count
if (counts[item]) {
counts[item] += 1;
} else {
// Otherwise initialise it
counts[item] = 1;
}
}

return counts;
Comment on lines +17 to +30
Copy link
Contributor

Choose a reason for hiding this comment

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

Does the following function call returns the value you expect?

tally(["toString", "toString"]);

Suggestion: Look up an approach to create an empty object with no inherited properties.

}

module.exports = tally;
Loading
Loading