Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 11 additions & 1 deletion Sprint-2/debug/address.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
// Predict and explain first...

/*
prediction: My house number is undefined
Reason: the code is trying to access the key 0 of the address object, which
does not exist.

Object properties aren't accessible by index. Author likely tried to treat it
the same as an array. Arrays are a special type of object which allows indexing into (some)
of its values.
*/

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

console.log(`My house number is ${address[0]}`);
console.log(`My house number is ${address.houseNumber}`);
9 changes: 8 additions & 1 deletion Sprint-2/debug/author.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
// Predict and explain first...
/*
Prediction: TypeError: author is not iterable

Cause: Plain Js objects aren't iterable the way an array's elements are.
To iterate over keys and/or values use the Object.keys(), Object.values(),
or Object.entries() methods to covert the data into an array 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
Expand All @@ -11,6 +18,6 @@ const author = {
alive: true,
};

for (const value of author) {
for (const value of Object.values(author)) {
console.log(value);
}
10 changes: 9 additions & 1 deletion Sprint-2/debug/recipe.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
// Predict and explain first...

/*
Prediction: the ingredients: ${recipe} part will only print something like [Object object]

Cause: The code is attempting to print the recipe object itself, rather than the ingredients.
In Js, when printing objects, it doesn't print a string of key, pair values of its
properties. Should be printing the ingredients property, which is an array, which does print out its values.
*/

// 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?
Expand All @@ -12,4 +20,4 @@ const recipe = {

console.log(`${recipe.title} serves ${recipe.serves}
ingredients:
${recipe}`);
${recipe.ingredients}`);
11 changes: 10 additions & 1 deletion Sprint-2/implement/contains.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
function contains() {}
function isObject(item) {
return typeof item === "object" && item !== null && !Array.isArray(item);
}

function contains(object, key) {
if (isObject(object)) {
return Object.keys(object).includes(key);
}
throw new TypeError("Item is not plain object, Date, or Map");
}

module.exports = contains;
13 changes: 12 additions & 1 deletion Sprint-2/implement/contains.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,27 @@ as the object doesn't contains a key of 'c'
// Given an empty object
// When passed to contains
// Then it should return false
test.todo("contains on empty object returns false");
test("given an empty object, should return false", () => {
expect(contains({}, "a")).toEqual(false);
});

// Given an object with properties
// When passed to contains with an existing property name
// Then it should return true
test("given an object with a property, and property name that exists, should return true", () => {
expect(contains({ a: 12, b: 2 }, "a")).toEqual(true);
});

// Given an object with properties
// When passed to contains with a non-existent property name
// Then it should return false
test("given an object, and property that does not exist in object, should return false", () => {
expect(contains({ a: 12, b: 2 }, "c")).toEqual(false);
});

// Given invalid parameters like an array
// When passed to contains
// Then it should return false or throw an error
test("given invalid parameter, like an array, should return false or throw error", () => {
expect(() => contains(22, "a")).toThrow(TypeError);
});
24 changes: 22 additions & 2 deletions Sprint-2/implement/lookup.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
function createLookup() {
// implementation here
// check if arry is 2d, and each element is a k,v pair and k,v both strings
function is2dKeyValStringArray(arr) {
return (
Array.isArray(arr) &&
arr.every((element) => {
return (
Array.isArray(element) &&
element.length === 2 &&
typeof element[0] === "string" &&
typeof element[1] === "string"
);
})
);
}

function createLookup(countryAndCurrency) {
// bit lazy, should have tests for type, and content seperately,
// to throw more specific errors
if (!is2dKeyValStringArray(countryAndCurrency)) {
throw new Error("Input is not valid type and/or format");
}
return Object.fromEntries(countryAndCurrency);
}

module.exports = createLookup;
56 changes: 54 additions & 2 deletions Sprint-2/implement/lookup.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
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
Expand Down Expand Up @@ -33,3 +31,57 @@ It should return:
'CA': 'CAD'
}
*/

test("converts 2d array to object", () => {
expect(
createLookup([
["a", "x"],
["b", "y"],
["c", "z"],
])
).toEqual({ a: "x", b: "y", c: "z" });
});

test("convert empty array, to empty object", () => {
expect(createLookup([])).toEqual({});
});

test("convert single 2d array, to object with one key", () => {
expect(createLookup([["a", "wow"]])).toEqual({ a: "wow" });
});

// this is the behaviour of Object.fromEntries(), I keep it for consistency
test("when there are duplicate keys, later entries overwrite earlier entries", () => {
expect(
createLookup([
["a", "ff"],
["b", "gg"],
["a", "hh"],
])
).toEqual({ b: "gg", a: "hh" });
});

test("when input is not an array throw error", () => {
expect(() => createLookup("asdf")).toThrow();
expect(() => createLookup(1)).toThrow();
expect(() => createLookup({ a: 12 })).toThrow();
});

test("when input is not a 2d array throw error", () => {
expect(() => createLookup(["a", "b"])).toThrow();
expect(() => createLookup([["a", "b"]])).not.toThrow();
});

test("when length of internal array not 1 < arr.length < 3", () => {
expect(() => createLookup(["a"])).toThrow();
expect(() => createLookup(["a", "b", "c"])).toThrow();
});

test("when non-string k, v pairs throw error", () => {
expect(() =>
createLookup([
[1, 2],
[3, 4],
])
).toThrow();
});
42 changes: 37 additions & 5 deletions Sprint-2/implement/querystring.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,45 @@
function parseQueryString(queryString) {
const queryParams = {};
if (queryString.length === 0) {
return queryParams;
}
if (!queryString || typeof queryString !== "string") return queryParams;

if (queryString.trim() === "") return queryParams;

const keyValuePairs = queryString.split("&");

for (const pair of keyValuePairs) {
const [key, value] = pair.split("=");
queryParams[key] = value;
const firstEqualsIndex = pair.indexOf("=");
let key, value;

// Checks for pairs with key but no value like debug&verbose&name=Alice
// by keeping them, prevent loss of information
if (firstEqualsIndex === -1) {
key = pair.trim();
value = "";
} else {
// splits on first equal, but not any of the rest
key = pair.slice(0, firstEqualsIndex).trim();
value = pair.slice(firstEqualsIndex + 1).trim();
}

// skips next part if the ends up being an empty str after slice and trim
if (!key) continue;

/* use hasOwnProperty to prevent conflict with built-in/inherited properties
like "toString", if the query string has a "toString=value".

if a key already exists on queryParams, but is not an array, then create an array
and store the the old value and new value in the array
*/
if (Object.prototype.hasOwnProperty.call(queryParams, key)) {
if (queryParams[key] === value || queryParams[key].includes(value))
continue;
if (!Array.isArray(queryParams[key])) {
queryParams[key] = [queryParams[key]];
}
queryParams[key].push(value);
} else {
queryParams[key] = value;
}
}

return queryParams;
Expand Down
77 changes: 74 additions & 3 deletions Sprint-2/implement/querystring.test.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,83 @@
// 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.
// 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");

// empty string
test("empty query string returns {}", () => {
expect(parseQueryString("")).toEqual({});
});

// null or undefined
test("empty query string returns {}", () => {
expect(parseQueryString(null)).toEqual({});
expect(parseQueryString(undefined)).toEqual({});
expect(parseQueryString()).toEqual({});
});

// happy path
// single valid kv pair
test("a single k, v with no special chars return object with matching k, v pair", () => {
expect(parseQueryString("mykey=myvalue")).toEqual({ mykey: "myvalue" });
});
// mutiple valid kv pairs
test("multiple k, v pairs with no special chars return object with matching k, v pairs", () => {
expect(parseQueryString("mykey=myvalue&otherkey=othervalue&third=3")).toEqual(
{
mykey: "myvalue",
otherkey: "othervalue",
third: "3",
}
);
});

// special characters in key, can't handle = in key, as that is fundamentally unparsable
test("special character in key without URL encoding", () => {
expect(parseQueryString("my+key=myvalue")).toEqual({
"my+key": "myvalue",
});
});
// special characters in value
test("parses querystring values containing =", () => {
expect(parseQueryString("equation=x=y+1")).toEqual({
"equation": "x=y+1",
equation: "x=y+1",
});
});

// missing values
test("key with no value return object with the key and value of empty string", () => {
expect(parseQueryString("foo=")).toEqual({ foo: "" });
});

//valueless flags
test("when given valueless keys, keep the key with empty string as value", () => {
expect(parseQueryString("debug&verbose")).toEqual({ debug: "", verbose: "" });
});

// ignore keyless values
test("ignores pairs with no key", () => {
expect(parseQueryString("=value")).toEqual({});
});

// duplicate keys with different values (array)
test("collects duplicate keys into an array", () => {
expect(parseQueryString("color=red&color=blue")).toEqual({
color: ["red", "blue"],
});
});

// duplicate k,v pair
test("if k,v already exists, or value array for a key already as value, ignore", () => {
expect(parseQueryString("color=red&color=red")).toEqual({ color: "red" });
});

// white spaces
test("trims whitespace from keys and values", () => {
expect(parseQueryString("name= Alice &age= 30 ")).toEqual({
name: "Alice",
age: "30",
});
});
13 changes: 12 additions & 1 deletion Sprint-2/implement/tally.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
function tally() {}
function tally(list) {
if (!Array.isArray(list)) {
throw new TypeError("Invalid input: not an array");
}
return (itemCountMapping = list.reduce((acc, curr) => {
if (typeof curr !== "string" && curr !== "number") {
throw new Error(`Invalid element: ${curr} must be a string or number`);
}
acc[curr] = (acc[curr] ?? 0) + 1;
return acc;
}, {}));
}

module.exports = tally;
14 changes: 13 additions & 1 deletion Sprint-2/implement/tally.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,24 @@ const tally = require("./tally.js");
// 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("given empty array, returns 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("given array with duplicate elements, returns correct count of each item", () => {
expect(tally(["a", "a", "b"])).toEqual({ a: 2, b: 1 });
});

// Given an invalid input like a string
// When passed to tally
// Then it should throw an error
test("given invalid input like a string, throw error", () => {
expect(() => tally("asdf")).toThrow(TypeError);
});

test("elements of the list must be string or number, or throw error", () => {
expect(() => tally([{ a: 12 }, { b: "c" }, { a: 12 }])).toThrow();
});
Loading
Loading