Skip to content

Commit 9351328

Browse files
committed
Address mentor feedback for Sprint 2
1 parent 23ea297 commit 9351328

9 files changed

Lines changed: 255 additions & 112 deletions

File tree

Sprint-2/debug/recipe.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
// Predict and explain first...
22

33
// This program should log out the title, how many it serves and the ingredients.
4-
// Each ingredient should be logged on a new line
5-
// How can you fix it?
4+
// Each ingredient should be logged on a new line.
5+
// The original code tried to print the whole object, which resulted in "[object Object]".
66

77
const recipe = {
88
title: "bruschetta",
99
serves: 2,
1010
ingredients: ["olive oil", "tomatoes", "salt", "pepper"],
1111
};
1212

13-
console.log(`${recipe.title} serves ${recipe.serves}
14-
ingredients:
15-
${recipe}`);
13+
console.log(`${recipe.title} serves ${recipe.serves}`);
14+
console.log("ingredients:");
15+
console.log(recipe.ingredients.join("\n"));

Sprint-2/implement/contains.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
1-
function contains() {}
1+
/**
2+
* contains()
3+
*
4+
* Checks whether an object contains a specific own property.
5+
*
6+
* @param {object} obj - The object to check.
7+
* @param {*} propertyName - The property name to check.
8+
* @returns {boolean} True if the object has the property as its own key, otherwise false.
9+
*/
10+
function contains(obj, propertyName) {
11+
// Reject null, non-objects, and arrays
12+
if (obj === null || typeof obj !== "object" || Array.isArray(obj)) {
13+
return false;
14+
}
15+
16+
// Object.hasOwn handles non-string property names by coercing them appropriately
17+
return Object.hasOwn(obj, propertyName);
18+
}
219

320
module.exports = contains;
Lines changed: 35 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,37 @@
11
const contains = require("./contains.js");
22

3-
/*
4-
Implement a function called contains that checks an object contains a
5-
particular property
6-
7-
E.g. contains({a: 1, b: 2}, 'a') // returns true
8-
as the object contains a key of 'a'
9-
10-
E.g. contains({a: 1, b: 2}, 'c') // returns false
11-
as the object doesn't contains a key of 'c'
12-
*/
13-
14-
// Acceptance criteria:
15-
16-
// Given a contains function
17-
// When passed an object and a property name
18-
// Then it should return true if the object contains the property, false otherwise
19-
20-
// Given an empty object
21-
// When passed to contains
22-
// Then it should return false
23-
test.todo("contains on empty object returns false");
24-
25-
// Given an object with properties
26-
// When passed to contains with an existing property name
27-
// Then it should return true
28-
29-
// Given an object with properties
30-
// When passed to contains with a non-existent property name
31-
// Then it should return false
32-
33-
// Given invalid parameters like an array
34-
// When passed to contains
35-
// Then it should return false or throw an error
3+
describe("contains()", () => {
4+
test("returns false for an empty object", () => {
5+
expect(contains({}, "a")).toBe(false);
6+
});
7+
8+
test("returns true when the property exists", () => {
9+
expect(contains({ a: 1, b: 2 }, "a")).toBe(true);
10+
});
11+
12+
test("returns false when the property does not exist", () => {
13+
expect(contains({ a: 1, b: 2 }, "c")).toBe(false);
14+
});
15+
16+
test("returns false for inherited properties", () => {
17+
expect(contains({ a: 1, b: 2 }, "toString")).toBe(false);
18+
});
19+
20+
test("returns false when given an array with a realistic array key", () => {
21+
expect(contains(["a", "b"], 0)).toBe(false);
22+
});
23+
24+
test("returns false when given null", () => {
25+
expect(contains(null, "a")).toBe(false);
26+
});
27+
28+
test("supports non-string property names", () => {
29+
const obj = { 3: 12 };
30+
expect(contains(obj, 3)).toBe(true);
31+
});
32+
33+
test("supports empty string as a property name", () => {
34+
const obj = { "": 99 };
35+
expect(contains(obj, "")).toBe(true);
36+
});
37+
});

Sprint-2/implement/lookup.js

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,31 @@
1-
function createLookup() {
2-
// implementation here
1+
/**
2+
* createLookup()
3+
*
4+
* Converts an array of [key, value] pairs into a lookup object.
5+
*
6+
* Example:
7+
* [['US', 'USD'], ['CA', 'CAD']]
8+
*
9+
* Returns:
10+
* { US: 'USD', CA: 'CAD' }
11+
*/
12+
function createLookup(pairs) {
13+
if (!Array.isArray(pairs)) {
14+
throw new Error("Expected an array of pairs");
15+
}
16+
17+
const lookup = {};
18+
19+
for (const pair of pairs) {
20+
if (!Array.isArray(pair) || pair.length !== 2) {
21+
throw new Error("Each item must be a [key, value] pair");
22+
}
23+
24+
const [key, value] = pair;
25+
lookup[key] = value;
26+
}
27+
28+
return lookup;
329
}
430

531
module.exports = createLookup;

Sprint-2/implement/lookup.test.js

Lines changed: 38 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,40 @@
11
const createLookup = require("./lookup.js");
22

3-
test.todo("creates a country currency code lookup for multiple codes");
4-
5-
/*
6-
7-
Create a lookup object of key value pairs from an array of code pairs
8-
9-
Acceptance Criteria:
10-
11-
Given
12-
- An array of arrays representing country code and currency code pairs
13-
e.g. [['US', 'USD'], ['CA', 'CAD']]
14-
15-
When
16-
- createLookup function is called with the country-currency array as an argument
17-
18-
Then
19-
- It should return an object where:
20-
- The keys are the country codes
21-
- The values are the corresponding currency codes
22-
23-
Example
24-
Given: [['US', 'USD'], ['CA', 'CAD']]
25-
26-
When
27-
createLookup(countryCurrencyPairs) is called
28-
29-
Then
30-
It should return:
31-
{
32-
'US': 'USD',
33-
'CA': 'CAD'
34-
}
35-
*/
3+
describe("createLookup()", () => {
4+
test("creates a country currency code lookup for multiple codes", () => {
5+
const pairs = [
6+
["US", "USD"],
7+
["CA", "CAD"],
8+
["GB", "GBP"],
9+
];
10+
11+
expect(createLookup(pairs)).toEqual({
12+
US: "USD",
13+
CA: "CAD",
14+
GB: "GBP",
15+
});
16+
});
17+
18+
test("returns an empty object for an empty array", () => {
19+
expect(createLookup([])).toEqual({});
20+
});
21+
22+
test("overwrites duplicate keys with the last value", () => {
23+
const pairs = [
24+
["US", "USD"],
25+
["US", "USN"],
26+
];
27+
28+
expect(createLookup(pairs)).toEqual({
29+
US: "USN",
30+
});
31+
});
32+
33+
test("throws an error when input is not an array", () => {
34+
expect(() => createLookup("invalid")).toThrow();
35+
});
36+
37+
test("throws an error when a pair is malformed", () => {
38+
expect(() => createLookup([["US", "USD"], ["CA"]])).toThrow();
39+
});
40+
});

Sprint-2/implement/querystring.js

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,38 @@
1+
/**
2+
* parseQueryString()
3+
*
4+
* Parses a query string into an object of decoded key-value pairs.
5+
*/
16
function parseQueryString(queryString) {
2-
const queryParams = {};
3-
if (queryString.length === 0) {
7+
const queryParams = Object.create(null);
8+
9+
if (typeof queryString !== "string" || queryString.length === 0) {
410
return queryParams;
511
}
12+
613
const keyValuePairs = queryString.split("&");
714

815
for (const pair of keyValuePairs) {
9-
const [key, value] = pair.split("=");
16+
if (pair === "") {
17+
continue;
18+
}
19+
20+
const separatorIndex = pair.indexOf("=");
21+
22+
let rawKey;
23+
let rawValue;
24+
25+
if (separatorIndex === -1) {
26+
rawKey = pair;
27+
rawValue = "";
28+
} else {
29+
rawKey = pair.slice(0, separatorIndex);
30+
rawValue = pair.slice(separatorIndex + 1);
31+
}
32+
33+
const key = decodeURIComponent(rawKey);
34+
const value = decodeURIComponent(rawValue);
35+
1036
queryParams[key] = value;
1137
}
1238

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,54 @@
11
// In the prep, we implemented a function to parse query strings.
2-
// Unfortunately, it contains several bugs!
3-
// Below is one test case for an edge case the implementation doesn't handle well.
4-
// Fix the implementation for this test, and try to think of as many other edge cases as possible - write tests and fix those too.
2+
// Unfortunately, it contains several bugs.
3+
// These tests cover the original bug and additional edge cases.
54

6-
const parseQueryString = require("./querystring.js")
5+
const parseQueryString = require("./querystring.js");
76

8-
test("parses querystring values containing =", () => {
9-
expect(parseQueryString("equation=x=y+1")).toEqual({
10-
"equation": "x=y+1",
7+
describe("parseQueryString()", () => {
8+
test("parses querystring values containing =", () => {
9+
expect(parseQueryString("equation=x=y+1")).toEqual({
10+
equation: "x=y+1",
11+
});
12+
});
13+
14+
test("returns an empty object for an empty string", () => {
15+
expect(parseQueryString("")).toEqual({});
16+
});
17+
18+
test("parses a single key-value pair", () => {
19+
expect(parseQueryString("name=Richard")).toEqual({
20+
name: "Richard",
21+
});
22+
});
23+
24+
test("parses multiple key-value pairs", () => {
25+
expect(parseQueryString("name=Richard&city=Sheffield")).toEqual({
26+
name: "Richard",
27+
city: "Sheffield",
28+
});
29+
});
30+
31+
test("handles a key with an empty value", () => {
32+
expect(parseQueryString("name=")).toEqual({
33+
name: "",
34+
});
35+
});
36+
37+
test("handles a key with no equals sign", () => {
38+
expect(parseQueryString("name")).toEqual({
39+
name: "",
40+
});
41+
});
42+
43+
test("ignores an empty trailing pair", () => {
44+
expect(parseQueryString("name=Richard&")).toEqual({
45+
name: "Richard",
46+
});
47+
});
48+
49+
test("decodes URL-encoded keys and values", () => {
50+
expect(parseQueryString("tags%5B%5D=hello%20world")).toEqual({
51+
"tags[]": "hello world",
52+
});
1153
});
1254
});

Sprint-2/implement/tally.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,29 @@
1-
function tally() {}
1+
/**
2+
* tally()
3+
*
4+
* Counts how many times each item appears in an array.
5+
*
6+
* Example:
7+
* tally(["a", "a", "b", "c"])
8+
* returns { a: 2, b: 1, c: 1 }
9+
*/
10+
function tally(items) {
11+
if (!Array.isArray(items)) {
12+
throw new Error("Expected an array");
13+
}
14+
15+
// Create an object with no inherited properties
16+
const counts = Object.create(null);
17+
18+
for (const item of items) {
19+
if (counts[item] !== undefined) {
20+
counts[item] += 1;
21+
} else {
22+
counts[item] = 1;
23+
}
24+
}
25+
26+
return counts;
27+
}
228

329
module.exports = tally;

0 commit comments

Comments
 (0)