diff --git a/Sprint-2/debug/address.js b/Sprint-2/debug/address.js index 940a6af83..0886a5278 100644 --- a/Sprint-2/debug/address.js +++ b/Sprint-2/debug/address.js @@ -1,9 +1,3 @@ -// 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 - const address = { houseNumber: 42, street: "Imaginary Road", @@ -12,4 +6,4 @@ const address = { postcode: "XYZ 123", }; -console.log(`My house number is ${address[0]}`); +console.log(`My house number is ${address.houseNumber}`); diff --git a/Sprint-2/debug/author.js b/Sprint-2/debug/author.js index 8c2125977..925ea5f54 100644 --- a/Sprint-2/debug/author.js +++ b/Sprint-2/debug/author.js @@ -1,7 +1,6 @@ -// 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 +// explanation: +// The code could not be executed because the for...of loop could not access object "author". +// for...loops are used in arrays, strings or with certain array methods. const author = { firstName: "Zadie", @@ -11,6 +10,6 @@ const author = { alive: true, }; -for (const value of author) { +for (const value of Object.values(author)) { console.log(value); } diff --git a/Sprint-2/debug/recipe.js b/Sprint-2/debug/recipe.js index 6cbdd22cd..e6230ac41 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -1,9 +1,3 @@ -// 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? - const recipe = { title: "bruschetta", serves: 2, @@ -12,4 +6,4 @@ const recipe = { console.log(`${recipe.title} serves ${recipe.serves} ingredients: -${recipe}`); +${recipe.ingredients.join("\n")}`); diff --git a/Sprint-2/implement/contains.js b/Sprint-2/implement/contains.js index cd779308a..f96b86673 100644 --- a/Sprint-2/implement/contains.js +++ b/Sprint-2/implement/contains.js @@ -1,3 +1,8 @@ -function contains() {} +function contains(object, property) { + if (typeof object !== "object" || object === null || Array.isArray(object)) { + throw new Error("contains requires an object"); + } + return Object.hasOwn(object, property); +} module.exports = contains; diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index 719272787..2be0788d7 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -1,19 +1,5 @@ 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([1, 2, 3], 'a') throws Error("contains requires an object") -as an array isn't an object -*/ - // Acceptance criteria: // Given a contains function @@ -23,18 +9,47 @@ as an array isn't an object // Given an empty object // When passed to contains // Then it should return false -test.todo("contains on empty object returns false"); +test("contains on empty object returns false", () => { + expect(contains({}, "name")).toBe(false); +}); // Given an object with properties // When passed to contains with an existing property name // Then it should return true +test("contains on object with existing property returns true", () => { + expect(contains({ name: "Moses", age: 30 }, "name")).toBe(true); +}); // Given an object with properties // When passed to contains with a non-existent property name // Then it should return false +test("contains on object with non-existent property returns false", () => { + expect(contains({ name: "Moses", age: 30 }, "city")).toBe(false); +}); // Given a value that isn't an object - an array, a string, a number, // null, or no argument at all // When passed to contains // Then it should throw Error("contains requires an object") // (careful: typeof [] and typeof null are both "object") +test("contains throws an error for an array", () => { + expect(() => contains([1, 2, 3], "0")).toThrow("contains requires an object"); +}); + +test("contains throws an error for a string", () => { + expect(() => contains("Manchester", "0")).toThrow( + "contains requires an object" + ); +}); + +test("contains throws an error for a number", () => { + expect(() => contains(30, "name")).toThrow("contains requires an object"); +}); + +test("contains throws an error for null", () => { + expect(() => contains(null, "name")).toThrow("contains requires an object"); +}); + +test("contains throws an error when no argument is given", () => { + expect(() => contains()).toThrow("contains requires an object"); +}); diff --git a/Sprint-2/implement/lookup.js b/Sprint-2/implement/lookup.js index a6746e07f..8d214aa97 100644 --- a/Sprint-2/implement/lookup.js +++ b/Sprint-2/implement/lookup.js @@ -1,5 +1,13 @@ -function createLookup() { - // implementation here +function createLookup(pairs) { + const lookup = {}; + + for (const pair of pairs) { + const countryCode = pair[0]; + const currencyCode = pair[1]; + + lookup[countryCode] = currencyCode; + } + return lookup; } module.exports = createLookup; diff --git a/Sprint-2/implement/lookup.test.js b/Sprint-2/implement/lookup.test.js index 547e06c5a..ada04588c 100644 --- a/Sprint-2/implement/lookup.test.js +++ b/Sprint-2/implement/lookup.test.js @@ -1,35 +1,11 @@ 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", () => { + const pairs = [ + ["US", "USD"], + ["CA", "CAD"], + ]; + const result = createLookup(pairs); + + expect(result).toEqual({ US: "USD", CA: "CAD" }); +}); diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 45ec4e5f3..e457f3e56 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -1,3 +1,12 @@ +function decode(text) { + const withSpaces = text.replace(/\+/g, " "); + try { + return decodeURIComponent(withSpaces); + } catch { + return withSpaces; + } +} + function parseQueryString(queryString) { const queryParams = {}; if (queryString.length === 0) { @@ -6,8 +15,25 @@ function parseQueryString(queryString) { const keyValuePairs = queryString.split("&"); for (const pair of keyValuePairs) { - const [key, value] = pair.split("="); - queryParams[key] = value; + if (pair === "") continue; + + const index = pair.indexOf("="); + const separator = index !== -1; + const rawKey = separator ? pair.slice(0, index) : pair; + const rawValue = separator ? pair.slice(index + 1) : ""; + + const key = decode(rawKey); + const value = decode(rawValue); + + if (key in queryParams) { + if (Array.isArray(queryParams[key])) { + queryParams[key].push(value); + } else { + queryParams[key] = [queryParams[key], value]; + } + } else { + queryParams[key] = value; + } } return queryParams; diff --git a/Sprint-2/implement/querystring.test.js b/Sprint-2/implement/querystring.test.js index 328b8df61..adbfc2965 100644 --- a/Sprint-2/implement/querystring.test.js +++ b/Sprint-2/implement/querystring.test.js @@ -3,7 +3,7 @@ // Below are some test cases the implementation doesn't handle well. // Fix the implementation for these tests, 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"); test("should parse values containing '='", () => { expect(parseQueryString("equation=a=b-2")).toEqual({ @@ -46,3 +46,8 @@ test("should store values of a key in an array when the key has 2 or more values foo: "bar", }); }); + +// Edge cases +test("should not crash on malformed percent-encoding", () => { + expect(parseQueryString("a=%E0%A4%A")).toEqual({ a: "%E0%A4%A" }); +}); diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index f47321812..e5dbcbcbf 100644 --- a/Sprint-2/implement/tally.js +++ b/Sprint-2/implement/tally.js @@ -1,3 +1,18 @@ -function tally() {} +function tally(items) { + if (!Array.isArray(items)) { + throw new Error("tally requires an array"); + } + + const counts = {}; + + for (const item of items) { + if (Object.hasOwn(counts, item)) { + counts[item] += 1; + } else { + counts[item] = 1; + } + } + return counts; +} module.exports = tally; diff --git a/Sprint-2/implement/tally.test.js b/Sprint-2/implement/tally.test.js index ca763c296..3ef40e625 100644 --- a/Sprint-2/implement/tally.test.js +++ b/Sprint-2/implement/tally.test.js @@ -1,19 +1,5 @@ const tally = require("./tally.js"); -/** - * tally array - * - * In this task, you'll need to implement a function called tally - * that will take a list of items and count the frequency of each item - * in an array - * - * For example: - * - * tally(['a']), target output: { a: 1 } - * tally(['a', 'a', 'a']), target output: { a: 3 } - * tally(['a', 'a', 'b', 'c']), target output: { a : 2, b: 1, c: 1 } - */ - // Acceptance criteria: // Given a function called tally @@ -23,12 +9,30 @@ 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("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("tally counts duplicate items", () => { + expect(tally(["a", "a", "a"])).toEqual({ a: 3 }); +}); +test("tally returns count for each unique item", () => { + expect(tally(["a", "a", "b", "c"])).toEqual({ a: 2, b: 1, c: 1 }); +}); // Given an invalid input like a string, a number, or no argument at all // When passed to tally // Then it should throw Error("tally requires an array") +test("tally throws an error when given an invalid input", () => { + expect(() => tally("hello")).toThrow(new Error("tally requires an array")); + expect(() => tally(30)).toThrow(new Error("tally requires an array")); + expect(() => tally()).toThrow(new Error("tally requires an array")); +}); + +// Tally handles items named like built-in object properties +test("tally handles items named like built-in object properties", () => { + expect(tally(["toString", "toString"])).toEqual({ toString: 2 }); +}); diff --git a/Sprint-2/interpret/invert.js b/Sprint-2/interpret/invert.js index bb353fb1f..039621169 100644 --- a/Sprint-2/interpret/invert.js +++ b/Sprint-2/interpret/invert.js @@ -1,29 +1,30 @@ -// Let's define how invert should work - -// Given an object -// When invert is passed this object -// Then it should swap the keys and values in the object - -// E.g. invert({x : 10, y : 20}), target output: {"10": "x", "20": "y"} - function invert(obj) { const invertedObj = {}; for (const [key, value] of Object.entries(obj)) { - invertedObj.key = value; + invertedObj[value] = key; } return invertedObj; } +module.exports = invert; + // a) What is the current return value when invert is called with { a : 1 } +// Current return value is an object, { key: 1 } // b) What is the current return value when invert is called with { a: 1, b: 2 } +// The current return value is an object also, { key: 2 } // c) What is the target return value when invert is called with {a : 1, b: 2} +// The target value is { "1": "a", "2": "b"} // c) What does Object.entries return? Why is it needed in this program? +// Object.entries returns an array of object [key, value] pairs. +// It converts an object into an array, so the loop works, since plain objects can't be used with loops. // d) Explain why the current return value is different from the target output +// It differs due to the fact that "invertedObj.key" uses the wrong notation, "key", instead of the variable's value. +// Also, adding brackets to the literal name, [key], would only copy the object not fix it. // e) Fix the implementation of invert (and write tests to prove it's fixed!) diff --git a/Sprint-2/interpret/invert.test.js b/Sprint-2/interpret/invert.test.js new file mode 100644 index 000000000..1b3933ba8 --- /dev/null +++ b/Sprint-2/interpret/invert.test.js @@ -0,0 +1,13 @@ +const invert = require("./invert"); + +test("swaps the key and value of a single pair", () => { + expect(invert({ a: 1 })).toEqual({ 1: "a" }); +}); + +test("swaps every pair when there are multiple pairs", () => { + expect(invert({ x: 10, y: 20 })).toEqual({ 10: "x", 20: "y" }); +}); + +test("returns an empty object when given an empty object", () => { + expect(invert({})).toEqual({}); +});