Skip to content

Commit 2438407

Browse files
committed
feat: implement contains, createLookup, parseQueryString, and tally functions with corresponding tests
1 parent cf1922f commit 2438407

File tree

8 files changed

+171
-29
lines changed

8 files changed

+171
-29
lines changed

Sprint-2/implement/contains.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1-
function contains() {}
1+
function contains(object, propertyName) {
2+
if (typeof object === "object" && object !== null) {
3+
return propertyName in object;
4+
}
5+
return false;
6+
}
27

38
module.exports = contains;

Sprint-2/implement/contains.test.js

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,39 @@ as the object doesn't contains a key of 'c'
1616
// Given a contains function
1717
// When passed an object and a property name
1818
// Then it should return true if the object contains the property, false otherwise
19+
test("returns true if the object contains the property, false otherwise", () => {
20+
const obj = { a: 1, b: 2 };
21+
22+
expect(contains(obj, "a")).toBe(true); // property exists
23+
expect(contains(obj, "b")).toBe(true); // property exists
24+
expect(contains(obj, "c")).toBe(false); // property does not exist
25+
});
1926

2027
// Given an empty object
2128
// When passed to contains
2229
// Then it should return false
23-
test.todo("contains on empty object returns false");
30+
test("contains on empty object returns false", () => {
31+
expect(contains({}, "a")).toBe(false);
32+
});
2433

2534
// Given an object with properties
2635
// When passed to contains with an existing property name
2736
// Then it should return true
28-
37+
test("returns true for existing property", () => {
38+
expect(contains({ a: 1, b: 2 }, "a")).toBe(true);
39+
});
2940
// Given an object with properties
3041
// When passed to contains with a non-existent property name
3142
// Then it should return false
32-
43+
test("returns false for missing property", () => {
44+
expect(contains({ a: 1, b: 2 }, "c")).toBe(false);
45+
});
3346
// Given invalid parameters like an array
3447
// When passed to contains
3548
// Then it should return false or throw an error
49+
test("returns false for invalid inputs like arrays", () => {
50+
expect(contains([], "a")).toBe(false);
51+
expect(contains(null, "a")).toBe(false);
52+
expect(contains(123, "a")).toBe(false);
53+
expect(contains("hello", "a")).toBe(false);
54+
});

Sprint-2/implement/lookup.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1-
function createLookup() {
1+
function createLookup(pairs) {
22
// implementation here
3+
const lookup = {};
4+
5+
for (const [country, currency] of pairs) {
6+
lookup[country] = currency;
7+
}
8+
9+
return lookup;
310
}
411

512
module.exports = createLookup;

Sprint-2/implement/lookup.test.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
11
const createLookup = require("./lookup.js");
22

3-
test.todo("creates a country currency code lookup for multiple codes");
3+
test("creates a country currency code lookup for multiple codes", () => {
4+
const input = [
5+
["US", "USD"],
6+
["CA", "CAD"],
7+
];
8+
9+
const expected = {
10+
US: "USD",
11+
CA: "CAD",
12+
};
13+
14+
expect(createLookup(input)).toEqual(expected);
15+
});
416

517
/*
618

Sprint-2/implement/querystring.js

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,52 @@
11
function parseQueryString(queryString) {
22
const queryParams = {};
3-
if (queryString.length === 0) {
3+
4+
// 1. Input Validation: Return empty object for non-string, null, or empty input.
5+
if (typeof queryString !== "string" || !queryString) {
46
return queryParams;
57
}
6-
const keyValuePairs = queryString.split("&");
8+
9+
// Remove leading '?' if it exists (e.g., if passed directly from window.location.search)
10+
const cleanedString = queryString.startsWith("?")
11+
? queryString.substring(1)
12+
: queryString;
13+
14+
const keyValuePairs = cleanedString.split("&");
715

816
for (const pair of keyValuePairs) {
9-
const [key, value] = pair.split("=");
10-
queryParams[key] = value;
17+
if (pair.length === 0) continue; // Skip empty pairs
18+
19+
const separatorIndex = pair.indexOf("=");
20+
let key;
21+
let value;
22+
23+
if (separatorIndex === -1) {
24+
// Case 1: Key with no equals sign (e.g., "key")
25+
key = pair;
26+
value = "";
27+
} else {
28+
// Case 2: Key/value pair with or without extra '=' in value (e.g., "eq=a=b")
29+
key = pair.substring(0, separatorIndex);
30+
// Value is everything after the first '='
31+
value = pair.substring(separatorIndex + 1);
32+
}
33+
34+
// Decode the key and value
35+
const decodedKey = decodeURIComponent(key);
36+
const decodedValue = decodeURIComponent(value);
37+
38+
// Handle Duplicate Keys: If the key already exists, convert the value to an array.
39+
if (queryParams.hasOwnProperty(decodedKey)) {
40+
if (Array.isArray(queryParams[decodedKey])) {
41+
queryParams[decodedKey].push(decodedValue);
42+
} else {
43+
// Convert existing value and new value to an array
44+
queryParams[decodedKey] = [queryParams[decodedKey], decodedValue];
45+
}
46+
} else {
47+
// Assign the value for the first time
48+
queryParams[decodedKey] = decodedValue;
49+
}
1150
}
1251

1352
return queryParams;

Sprint-2/implement/querystring.test.js

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,35 @@
33
// Below is one test case for an edge case the implementation doesn't handle well.
44
// Fix the implementation for this test, and try to think of as many other edge cases as possible - write tests and fix those too.
55

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

88
test("parses querystring values containing =", () => {
99
expect(parseQueryString("equation=x=y+1")).toEqual({
10-
"equation": "x=y+1",
10+
equation: "x=y+1",
1111
});
1212
});
13+
test("handles URL encoding in keys and values", () => {
14+
expect(parseQueryString("search=hello%20world&sort=d%26a")).toEqual({
15+
search: "hello world",
16+
sort: "d&a",
17+
});
18+
});
19+
test("handles duplicate keys by creating an array of values", () => {
20+
expect(parseQueryString("color=red&color=blue&size=m")).toEqual({
21+
color: ["red", "blue"],
22+
size: "m",
23+
});
24+
});
25+
test("handles keys with empty or missing values", () => {
26+
expect(parseQueryString("key1=&key2=value&key3&key4=")).toEqual({
27+
key1: "",
28+
key2: "value",
29+
key3: "",
30+
key4: "",
31+
});
32+
});
33+
test("handles empty string, leading question mark, and malformed input", () => {
34+
expect(parseQueryString("")).toEqual({});
35+
expect(parseQueryString("?a=1")).toEqual({ a: "1" });
36+
expect(parseQueryString("a=1&&b=2")).toEqual({ a: "1", b: "2" });
37+
});

Sprint-2/implement/tally.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1-
function tally() {}
1+
function tally(list) {
2+
// 1. Input Validation: Throw an error if the input is not a proper array.
3+
if (!Array.isArray(list)) {
4+
throw new Error("Input must be an array.");
5+
}
6+
7+
// 2. Tally Frequencies using reduce()
8+
return list.reduce((counts, item) => {
9+
// If the item exists in the object, increment its count.
10+
// Otherwise, initialize its count to 1.
11+
counts[item] = (counts[item] || 0) + 1;
12+
13+
return counts;
14+
}, {}); // Start with an empty object {}
15+
}
216

317
module.exports = tally;

Sprint-2/implement/tally.test.js

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,41 @@ const tally = require("./tally.js");
1515
*/
1616

1717
// Acceptance criteria:
18+
describe("tally", () => {
19+
// Given a function called tally
20+
// When passed an array of items
21+
// Then it should return an object containing the count for each unique item
22+
test("returns correct counts for mixed data types", () => {
23+
const input = [1, "a", 2, true, 1, "a", 2, true];
24+
const expectedOutput = { 1: 2, a: 2, 2: 2, true: 2 };
25+
expect(tally(input)).toEqual(expectedOutput);
26+
});
27+
// Given an empty array
28+
// When passed to tally
29+
// Then it should return an empty object
30+
test("tally on an empty array returns an empty object", () => {
31+
expect(tally([])).toEqual({});
32+
});
33+
// Given an array with duplicate items
34+
// When passed to tally
35+
// Then it should return counts for each unique item
36+
test("returns correct counts for an array with duplicates", () => {
37+
const input = ["banana", "apple", "banana", "cherry", "apple", "banana"];
38+
const expectedOutput = {
39+
banana: 3,
40+
apple: 2,
41+
cherry: 1,
42+
};
43+
expect(tally(input)).toEqual(expectedOutput);
44+
});
45+
// Given an invalid input like a string
46+
// When passed to tally
47+
// Then it should throw an error
1848

19-
// Given a function called tally
20-
// When passed an array of items
21-
// Then it should return an object containing the count for each unique item
22-
23-
// Given an empty array
24-
// When passed to tally
25-
// Then it should return an empty object
26-
test.todo("tally on an empty array returns an empty object");
27-
28-
// Given an array with duplicate items
29-
// When passed to tally
30-
// Then it should return counts for each unique item
31-
32-
// Given an invalid input like a string
33-
// When passed to tally
34-
// Then it should throw an error
49+
test.each(["not an array", 123, null, undefined, { key: "value" }])(
50+
"throws an error for invalid input type: %p",
51+
(invalidInput) => {
52+
expect(() => tally(invalidInput)).toThrow("Input must be an array.");
53+
}
54+
);
55+
});

0 commit comments

Comments
 (0)