diff --git a/Sprint-2/debug/address.js b/Sprint-2/debug/address.js index 940a6af83..38a2812de 100644 --- a/Sprint-2/debug/address.js +++ b/Sprint-2/debug/address.js @@ -1,4 +1,6 @@ // Predict and explain first... +// I predict that this code won't work because the house number is accessed using the wrong syntax. +// The house number is a property of the address object, so it should be accessed using dot notation. // This code should log out the houseNumber from the address object // but it isn't working... @@ -12,4 +14,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..a26a6aed1 100644 --- a/Sprint-2/debug/author.js +++ b/Sprint-2/debug/author.js @@ -1,4 +1,5 @@ // Predict and explain first... +// I predict it will throw an error because the for...of loop is trying to iterate over an object, object is not iterable. // 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 @@ -11,6 +12,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..626870b63 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -1,5 +1,6 @@ // Predict and explain first... - +// I predict that this code wont work because the way the ingredients property is being accessed is wrong. +// The ingredients property is an array, so it should be accessed using dot notation and need a method to print all elements. // 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? @@ -11,5 +12,5 @@ const recipe = { }; console.log(`${recipe.title} serves ${recipe.serves} - ingredients: -${recipe}`); +ingredients: +${recipe.ingredients.join("\n ")}`); diff --git a/Sprint-2/implement/contains.js b/Sprint-2/implement/contains.js index cd779308a..6f79ce219 100644 --- a/Sprint-2/implement/contains.js +++ b/Sprint-2/implement/contains.js @@ -1,3 +1,5 @@ -function contains() {} +function contains(obj, prop) { + return obj.hasOwnProperty(prop); +} module.exports = contains; diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index 326bdb1f2..3ec8d224f 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -11,25 +11,44 @@ E.g. contains({a: 1, b: 2}, 'c') // returns false as the object doesn't contains a key of 'c' */ -// 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 - -// Given an empty object -// When passed to contains -// Then it should return false -test.todo("contains on empty object returns false"); - -// Given an object with properties -// When passed to contains with an existing property name -// Then it should return true - -// Given an object with properties -// When passed to contains with a non-existent property name -// Then it should return false - -// Given invalid parameters like an array -// When passed to contains -// Then it should return false or throw an error +describe("contains", () => { + // 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 + test("contains returns true for existing property", () => { + const obj = { a: 1, b: 2 }; + expect(contains(obj, "a")).toEqual(true); + }); + + // Given an empty object + // When passed to contains + // Then it should return false + test("contains returns false for empty object", () => { + const obj = {}; + expect(contains(obj, "a")).toEqual(false); + }); + // Given an object with properties + // When passed to contains with an existing property name + // Then it should return true + test("contains returns true for existing property", () => { + const obj = { a: 1, b: 2 }; + expect(contains(obj, "a")).toEqual(true); + }); + + // Given an object with properties + // When passed to contains with a non-existent property name + // Then it should return false + test("contains returns false for non-existent property", () => { + const obj = { a: 1, b: 2 }; + expect(contains(obj, "c")).toEqual(false); + }); + // Given invalid parameters like an array + // When passed to contains + // Then it should return false or throw an error + test("contains returns false for invalid parameters", () => { + const arr = [1, 2, 3]; + expect(contains(arr, "a")).toEqual(false); + }); +}); diff --git a/Sprint-2/implement/lookup.js b/Sprint-2/implement/lookup.js index a6746e07f..c05e11850 100644 --- a/Sprint-2/implement/lookup.js +++ b/Sprint-2/implement/lookup.js @@ -1,5 +1,11 @@ -function createLookup() { +function createLookup(countryCurrencyPairs) { // implementation here + + const lookup = {}; + for (const [key, value] of countryCurrencyPairs) { + lookup[key] = value; + } + return lookup; } module.exports = createLookup; diff --git a/Sprint-2/implement/lookup.test.js b/Sprint-2/implement/lookup.test.js index 547e06c5a..192349ab2 100644 --- a/Sprint-2/implement/lookup.test.js +++ b/Sprint-2/implement/lookup.test.js @@ -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 @@ -33,3 +31,21 @@ It should return: 'CA': 'CAD' } */ + +describe("createLookup", () => { + test("creates a country currency code lookup for multiple codes", () => { + const countryCurrencyPairs = [ + ["US", "USD"], + ["CA", "CAD"], + ["GB", "GBP"], + ]; + + const expectedLookup = { + US: "USD", + CA: "CAD", + GB: "GBP", + }; + + expect(createLookup(countryCurrencyPairs)).toEqual(expectedLookup); + }); +}); diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 45ec4e5f3..bf741f2c5 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -1,13 +1,23 @@ function parseQueryString(queryString) { const queryParams = {}; + + if (queryString.startsWith("?")) { + queryString = queryString.slice(1); + } + if (queryString.length === 0) { return queryParams; } const keyValuePairs = queryString.split("&"); for (const pair of keyValuePairs) { - const [key, value] = pair.split("="); - queryParams[key] = value; + if (pair === "") { + continue; + } + + const [key, ...value] = pair.split("="); + + queryParams[key] = value.join("="); } return queryParams; diff --git a/Sprint-2/implement/querystring.test.js b/Sprint-2/implement/querystring.test.js index 3e218b789..142e2cbdf 100644 --- a/Sprint-2/implement/querystring.test.js +++ b/Sprint-2/implement/querystring.test.js @@ -3,10 +3,31 @@ // 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"); test("parses querystring values containing =", () => { expect(parseQueryString("equation=x=y+1")).toEqual({ - "equation": "x=y+1", + equation: "x=y+1", + }); +}); + +test("ignores a leading question mark", () => { + expect(parseQueryString("?name=zadie&age=40")).toEqual({ + name: "zadie", + age: "40", + }); +}); + +test("handles empty pairs caused by stray ampersands", () => { + expect(parseQueryString("a=1&&&b=2&")).toEqual({ + a: "1", + b: "2", + }); +}); + +test("handles keys that have no values", () => { + expect(parseQueryString("active&sort=desc")).toEqual({ + active: "", + sort: "desc", }); }); diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index f47321812..b1d689f99 100644 --- a/Sprint-2/implement/tally.js +++ b/Sprint-2/implement/tally.js @@ -1,3 +1,19 @@ -function tally() {} +function tally(list) { + if (!Array.isArray(list)) { + throw new Error("Input must be an array"); + } + + const counts = {}; + + for (const item of list) { + if (counts[item]) { + 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 2ceffa8dd..14af20b71 100644 --- a/Sprint-2/implement/tally.test.js +++ b/Sprint-2/implement/tally.test.js @@ -19,16 +19,32 @@ const tally = require("./tally.js"); // Given a function called tally // When passed an array of items // Then it should return an object containing the count for each unique item - +test("tally counts the frequency of items in an array", () => { + const input = ["a", "a", "b", "c"]; + const expectedOutput = { a: 2, b: 1, c: 1 }; + expect(tally(input)).toEqual(expectedOutput); +}); // 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", () => { + const input = []; + const expectedOutput = {}; + expect(tally(input)).toEqual(expectedOutput); +}); // Given an array with duplicate items // When passed to tally // Then it should return counts for each unique item +test("tally counts duplicate items correctly", () => { + const input = ["x", "y", "x", "z", "y", "x"]; + const expectedOutput = { x: 3, y: 2, z: 1 }; + expect(tally(input)).toEqual(expectedOutput); +}); // Given an invalid input like a string // When passed to tally // Then it should throw an error +test("tally throws an error for invalid input", () => { + expect(() => tally("not an array")).toThrow("Input must be an array"); +}); diff --git a/Sprint-2/interpret/invert.js b/Sprint-2/interpret/invert.js index bb353fb1f..8fb05c1aa 100644 --- a/Sprint-2/interpret/invert.js +++ b/Sprint-2/interpret/invert.js @@ -6,24 +6,39 @@ // E.g. invert({x : 10, y : 20}), target output: {"10": "x", "20": "y"} -function invert(obj) { - const invertedObj = {}; +// function invert(obj) { +// const invertedObj = {}; - for (const [key, value] of Object.entries(obj)) { - invertedObj.key = value; - } +// for (const [key, value] of Object.entries(obj)) { +// invertedObj.key = value; +// } - return invertedObj; -} +// return invertedObj; +// } // a) What is the current return value when invert is called with { a : 1 } +// my guess is that it will return { key: 1 } because the code is setting the property "key" on the invertedObj to the value of 1. // b) What is the current return value when invert is called with { a: 1, b: 2 } +// { key : 2 } because the loop will overwrite the "key" property with the value of 2 after processing the second key-value pair. // c) What is the target return value when invert is called with {a : 1, b: 2} +// target output: {1: "a", 2: "b"} + +// d) What does Object.entries return? Why is it needed in this program? +// Object.entries returns an array of arrays from an object. It's needed to iterate over each property in the object. -// c) What does Object.entries return? Why is it needed in this program? +// e) Explain why the current return value is different from the target output +// because the current implementation is using key as a literal property name instead of using the value of the variable key with bracket notation. +// f) Fix the implementation of invert (and write tests to prove it's fixed!) +function invert(obj) { + const invertedObj = {}; -// d) Explain why the current return value is different from the target output + for (const [key, value] of Object.entries(obj)) { + invertedObj[value] = key; + } + + return invertedObj; +} -// e) Fix the implementation of invert (and write tests to prove it's fixed!) +module.exports = invert; diff --git a/Sprint-2/interpret/invert.test.js b/Sprint-2/interpret/invert.test.js new file mode 100644 index 000000000..17f5cb5b2 --- /dev/null +++ b/Sprint-2/interpret/invert.test.js @@ -0,0 +1,25 @@ +const invert = require("./invert"); + +test("invert returns an object with keys and values swapped, simple case", () => { + const input = { a: 1 }; + const expectedOutput = { 1: "a" }; + expect(invert(input)).toEqual(expectedOutput); +}); + +test("invert returns an object with keys and values swapped, multiple pairs", () => { + const input = { a: 1, b: 2 }; + const expectedOutput = { 1: "a", 2: "b" }; + expect(invert(input)).toEqual(expectedOutput); +}); + +test("invert handles empty objects", () => { + const input = {}; + const expectedOutput = {}; + expect(invert(input)).toEqual(expectedOutput); +}); + +test("invert handles non-string values", () => { + const input = { x: true, y: null }; + const expectedOutput = { true: "x", null: "y" }; + expect(invert(input)).toEqual(expectedOutput); +}); diff --git a/Sprint-2/stretch/count-words.js b/Sprint-2/stretch/count-words.js index 8e85d19d7..01a1749f6 100644 --- a/Sprint-2/stretch/count-words.js +++ b/Sprint-2/stretch/count-words.js @@ -26,3 +26,31 @@ 3. Order the results to find out which word is the most common in the input */ + +function countWords(str) { + const wordCounts = {}; + + const cleanedStr = str + .replace(/[.,!?&@?]+/g, "") + .toLowerCase() + .trim(); + + const words = cleanedStr.split(/\s+/); + + for (const word of words) { + if (word === "") { + continue; + } + if (wordCounts[word]) { + wordCounts[word] += 1; + } else { + wordCounts[word] = 1; + } + } + + return wordCounts; +} + +console.log(countWords("you a?nd? me ??and&@?you")); + +module.exports = countWords; diff --git a/Sprint-2/stretch/count-words.test.js b/Sprint-2/stretch/count-words.test.js new file mode 100644 index 000000000..51821ec7e --- /dev/null +++ b/Sprint-2/stretch/count-words.test.js @@ -0,0 +1,37 @@ +const countWords = require("./count-words"); + +test("counts the number of times each word appears in a string", () => { + const input = "you and me and you"; + const expectedOutput = { you: 2, and: 2, me: 1 }; + expect(countWords(input)).toEqual(expectedOutput); +}); + +test("handles punctuation and case sensitivity", () => { + const input = "Hello, hello! How are you? You look well."; + const expectedOutput = { hello: 2, how: 1, are: 1, you: 2, look: 1, well: 1 }; + expect(countWords(input)).toEqual(expectedOutput); +}); + +test("handles an empty string", () => { + const input = ""; + const expectedOutput = {}; + expect(countWords(input)).toEqual(expectedOutput); +}); + +test("handles a string with only punctuation", () => { + const input = "!!!,,,???"; + const expectedOutput = {}; + expect(countWords(input)).toEqual(expectedOutput); +}); + +test("handles a string with multiple spaces", () => { + const input = " you and me "; + const expectedOutput = { you: 1, and: 1, me: 1 }; + expect(countWords(input)).toEqual(expectedOutput); +}); + +test("handles a string with numbers and special characters", () => { + const input = "hello123 world! @hello"; + const expectedOutput = { hello123: 1, world: 1, hello: 1 }; + expect(countWords(input)).toEqual(expectedOutput); +});