From 7d8f40217b35f51aaa9e271c5627a222a2a27cf0 Mon Sep 17 00:00:00 2001 From: ChinweP Date: Mon, 27 Jul 2026 16:02:59 +0100 Subject: [PATCH 01/11] Fix calculateMedian implementation so it passes all tests --- Sprint-1/fix/median.js | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/Sprint-1/fix/median.js b/Sprint-1/fix/median.js index b22590bc6..db759d5ba 100644 --- a/Sprint-1/fix/median.js +++ b/Sprint-1/fix/median.js @@ -6,9 +6,31 @@ // or 'list' has mixed values (the function is expected to sort only numbers). function calculateMedian(list) { - const middleIndex = Math.floor(list.length / 2); - const median = list.splice(middleIndex, 1)[0]; - return median; + // Validate input: must be an array + if (!Array.isArray(list)) { + return null; + } + + // Filter out non-numeric values + const numbers = list.filter((item) => typeof item === "number"); + + // If no numeric values, return null + if (numbers.length === 0) { + return null; + } + + // Sort numbers without mutating original list + const sorted = [...numbers].sort((a, b) => a - b); + + const middleIndex = Math.floor(sorted.length / 2); + + // Odd length β†’ return middle number + if (sorted.length % 2 !== 0) { + return sorted[middleIndex]; + } + + // Even length β†’ average of two middle numbers + return (sorted[middleIndex - 1] + sorted[middleIndex]) / 2; } module.exports = calculateMedian; From 46499633bcc3b0d67808e9fd9efb7b8910ec8787 Mon Sep 17 00:00:00 2001 From: ChinweP Date: Mon, 27 Jul 2026 16:33:57 +0100 Subject: [PATCH 02/11] Add dedupe implementation that passes all current tests --- Sprint-1/implement/dedupe.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Sprint-1/implement/dedupe.js b/Sprint-1/implement/dedupe.js index 781e8718a..209465014 100644 --- a/Sprint-1/implement/dedupe.js +++ b/Sprint-1/implement/dedupe.js @@ -1 +1,5 @@ -function dedupe() {} +function dedupe(arr) { + return [...new Set(arr)]; +} + +module.exports = dedupe; \ No newline at end of file From bb247fd85a200cb06047f8fe029766a8c81c3e7d Mon Sep 17 00:00:00 2001 From: ChinweP Date: Mon, 27 Jul 2026 17:05:14 +0100 Subject: [PATCH 03/11] Implement findMax and add full test coverage for all input scenarios --- Sprint-1/implement/max.js | 5 +++++ Sprint-1/implement/max.test.js | 23 ++++++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/Sprint-1/implement/max.js b/Sprint-1/implement/max.js index 6dd76378e..ceacf7c5a 100644 --- a/Sprint-1/implement/max.js +++ b/Sprint-1/implement/max.js @@ -1,4 +1,9 @@ function findMax(elements) { + const numbers = elements.filter((item) => typeof item === "number"); + if (numbers.length === 0) { + return -Infinity; + } + return Math.max(...numbers); } module.exports = findMax; diff --git a/Sprint-1/implement/max.test.js b/Sprint-1/implement/max.test.js index 82f18fd88..ebfabf0fa 100644 --- a/Sprint-1/implement/max.test.js +++ b/Sprint-1/implement/max.test.js @@ -16,28 +16,49 @@ const findMax = require("./max.js"); // When passed to the max function // Then it should return -Infinity // Delete this test.todo and replace it with a test. -test.todo("given an empty array, returns -Infinity"); +//test.todo("given an empty array, returns -Infinity"); +test("given an empty array, returns -Infinity", () => { + expect(findMax([])).toBe(-Infinity); +}); // Given an array with one number // When passed to the max function // Then it should return that number +test("given an array with one number, returns that number", () => { + expect(findMax([42])).toBe(42); +}); // Given an array with both positive and negative numbers // When passed to the max function // Then it should return the largest number overall +test("given an array with positive and negative numbers, returns the largest", () => { + expect(findMax([-10, 5, 3, -2])).toBe(5); +}); // Given an array with just negative numbers // When passed to the max function // Then it should return the closest one to zero +test("given an array with only negative numbers, returns the closest to zero", () => { + expect(findMax([-10, -3, -20])).toBe(-3); +}); // Given an array with decimal numbers // When passed to the max function // Then it should return the largest decimal number +test("given an array with decimal numbers, returns the largest decimal", () => { + expect(findMax([1.2, 3.5, 2.8])).toBe(3.5); +}); // Given an array with non-number values // When passed to the max function // Then it should return the max and ignore non-numeric values +test("given an array with non-number values, ignores them and returns the max", () => { + expect(findMax(['hey', 10, 'hi', 60, 10])).toBe(60); +}); // Given an array with only non-number values // When passed to the max function // Then it should return the least surprising value given how it behaves for all other inputs +test("given an array with only non-number values, returns -Infinity", () => { + expect(findMax(['a', 'b', null, undefined])).toBe(-Infinity); +}); \ No newline at end of file From 816b1041eb978c9b1316554f62dbe666734a286b Mon Sep 17 00:00:00 2001 From: ChinweP Date: Mon, 27 Jul 2026 18:51:37 +0100 Subject: [PATCH 04/11] Implement sum function and add full test coverage --- Sprint-1/implement/sum.js | 5 +++++ Sprint-1/implement/sum.test.js | 20 +++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/Sprint-1/implement/sum.js b/Sprint-1/implement/sum.js index 9062aafe3..7d4ec4186 100644 --- a/Sprint-1/implement/sum.js +++ b/Sprint-1/implement/sum.js @@ -1,4 +1,9 @@ function sum(elements) { + const numbers = elements.filter((item) => typeof item === "number"); + if (numbers.length === 0) { + return 0; + } + return numbers.reduce((total, num) => total + num, 0); } module.exports = sum; diff --git a/Sprint-1/implement/sum.test.js b/Sprint-1/implement/sum.test.js index dd0a090ca..32bab4ade 100644 --- a/Sprint-1/implement/sum.test.js +++ b/Sprint-1/implement/sum.test.js @@ -13,24 +13,42 @@ const sum = require("./sum.js"); // Given an empty array // When passed to the sum function // Then it should return 0 -test.todo("given an empty array, returns 0") +// test.todo("given an empty array, returns 0") +test("given an empty array, returns 0", () => { + expect(sum([])).toBe(0); +}); // Given an array with just one number // When passed to the sum function // Then it should return that number +test("given an array with one number, returns that number", () => { + expect(sum([42])).toBe(42); +}); // Given an array containing negative numbers // When passed to the sum function // Then it should still return the correct total sum +test("given an array with negative numbers, returns the correct total", () => { + expect(sum([-5, 10, -3])).toBe(2); +}); // Given an array with decimal/float numbers // When passed to the sum function // Then it should return the correct total sum +test("given an array with decimal numbers, returns the correct total", () => { + expect(sum([1.5, 2.5, 3.1])).toBe(7.1); +}); // Given an array containing non-number values // When passed to the sum function // Then it should ignore the non-numerical values and return the sum of the numerical elements +test("given an array with non-number values, ignores them and returns the sum of numbers", () => { + expect(sum(["hey", 10, "hi", 60, 10])).toBe(80); +}); // Given an array with only non-number values // When passed to the sum function // Then it should return the least surprising value given how it behaves for all other inputs +test("given an array with only non-number values, returns 0", () => { + expect(sum(["a", null, undefined, "b"])).toBe(0); +}); From 64a2b806f781c961d1a8bf7e8dacff4390d5884d Mon Sep 17 00:00:00 2001 From: ChinweP Date: Mon, 27 Jul 2026 19:18:19 +0100 Subject: [PATCH 05/11] Refactor includes to use a for-of loop --- Sprint-1/refactor/includes.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Sprint-1/refactor/includes.js b/Sprint-1/refactor/includes.js index 29dad81f0..8c9ae2e66 100644 --- a/Sprint-1/refactor/includes.js +++ b/Sprint-1/refactor/includes.js @@ -1,8 +1,7 @@ // Refactor the implementation of includes to use a for...of loop function includes(list, target) { - for (let index = 0; index < list.length; index++) { - const element = list[index]; + for (const element of list) { if (element === target) { return true; } From b057112fe57c5e516f554cacdd3e0906502ff17b Mon Sep 17 00:00:00 2001 From: ChinweP Date: Mon, 27 Jul 2026 19:50:58 +0100 Subject: [PATCH 06/11] Fix address lookup by using correct object property --- Sprint-2/debug/address.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Sprint-2/debug/address.js b/Sprint-2/debug/address.js index 940a6af83..a3f63cd92 100644 --- a/Sprint-2/debug/address.js +++ b/Sprint-2/debug/address.js @@ -4,6 +4,19 @@ // but it isn't working... // Fix anything that isn't working +/*const address = { + houseNumber: 42, + street: "Imaginary Road", + city: "Manchester", + country: "England", + postcode: "XYZ 123", +}; + +console.log(`My house number is ${address[0]}`); */ + +// The line "address[0]" is wrong. Because address is an object not an array, so "address[0]" is undefined. The output will be "My house number is undefined". + +// Corrected code const address = { houseNumber: 42, street: "Imaginary Road", @@ -12,4 +25,4 @@ const address = { postcode: "XYZ 123", }; -console.log(`My house number is ${address[0]}`); +console.log(`My house number is ${address.houseNumber}`); \ No newline at end of file From c76ea742ae20bf6ead59dc5c4ba2cedd2451b68c Mon Sep 17 00:00:00 2001 From: ChinweP Date: Mon, 27 Jul 2026 20:32:00 +0100 Subject: [PATCH 07/11] Fix object iteration by using Object.values() instead of for-of --- Sprint-2/debug/author.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/Sprint-2/debug/author.js b/Sprint-2/debug/author.js index 8c2125977..37a05131d 100644 --- a/Sprint-2/debug/author.js +++ b/Sprint-2/debug/author.js @@ -3,7 +3,7 @@ // 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 -const author = { +/* const author = { firstName: "Zadie", lastName: "Smith", occupation: "writer", @@ -14,3 +14,21 @@ const author = { for (const value of author) { console.log(value); } +*/ + +// Prediction and Explanation +/* The line "for (const value of author) {console.log(value);}" will not work because for-of is used only for iterable things, e.g: arrays, strings, maps, sets. +But author is a plain object (created with {}), and plain objects are not iterable. So Javascript throws: TypeError: author is not iterable because objects don't have a natural order to loop through. */ + +// Corrected code +const author = { + firstName: "Zadie", + lastName: "Smith", + occupation: "writer", + age: 40, + alive: true, +}; + +for (const value of Object.values(author)) { + console.log(value); +} From 17433111761614b4254423460fb59b88c4c3ed58 Mon Sep 17 00:00:00 2001 From: ChinweP Date: Mon, 27 Jul 2026 23:49:25 +0100 Subject: [PATCH 08/11] Add correct ingredient logging by looping through recipe.ingredients --- Sprint-2/debug/recipe.js | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/Sprint-2/debug/recipe.js b/Sprint-2/debug/recipe.js index 6cbdd22cd..ab5c9d0cd 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -4,7 +4,7 @@ // Each ingredient should be logged on a new line // How can you fix it? -const recipe = { +/*const recipe = { title: "bruschetta", serves: 2, ingredients: ["olive oil", "tomatoes", "salt", "pepper"], @@ -13,3 +13,26 @@ const recipe = { console.log(`${recipe.title} serves ${recipe.serves} ingredients: ${recipe}`); +*/ + +// Prediction and Explanation +/* +The line inside the template string: ${recipe} does not print the ingredients. +Instead, JavaScript converts the entire recipe object into a string, which becomes "[object Object]". This happens +because plain objects {} are not automatically converted into readable text. The program should print each ingredient on a new line, but the current code +never loops through recipe.ingredients, so nothing is printed correctly. +*/ + +// Corrected code +const recipe = { + title: "bruschetta", + serves: 2, + ingredients: ["olive oil", "tomatoes", "salt", "pepper"], +}; + +console.log(`${recipe.title} serves ${recipe.serves} +ingredients:`); + +for (const ingredient of recipe.ingredients) { + console.log(ingredient); +} From 5ee54090fb639ed5e126c60fd1729872d9ba5c43 Mon Sep 17 00:00:00 2001 From: ChinweP Date: Tue, 28 Jul 2026 01:30:22 +0100 Subject: [PATCH 09/11] Add jest tests covering all contains() acceptance criteria --- Sprint-2/implement/contains.js | 9 ++++++++- Sprint-2/implement/contains.test.js | 29 ++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/Sprint-2/implement/contains.js b/Sprint-2/implement/contains.js index cd779308a..05f18e140 100644 --- a/Sprint-2/implement/contains.js +++ b/Sprint-2/implement/contains.js @@ -1,3 +1,10 @@ -function contains() {} +function contains(obj, prop) { + if (typeof obj !== "object" || obj === null || Array.isArray(obj)) { + return false; + } + + 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..9a232aa56 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -16,20 +16,47 @@ as the object doesn't contains a key of 'c' // 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("returns true when object contains the property", () => { + const obj = { a: 1, b: 2 }; + expect(contains(obj, "a")).toBe(true); +}); + +test("returns false when object does not contain the property", () => { + const obj = { a: 1, b: 2 }; + expect(contains(obj, "c")).toBe(false); +}); + // Given an empty object // When passed to contains // Then it should return false -test.todo("contains on empty object returns false"); +// test.todo("contains on empty object returns false"); +test("contains on empty object returns false", () => { + expect(contains({}, "a")).toBe(false); +}); // Given an object with properties // When passed to contains with an existing property name // Then it should return true +test("returns true when object contains the property", () => { + const obj = { a: 1, b: 2 }; + expect(contains(obj, "a")).toBe(true); +}); // Given an object with properties // When passed to contains with a non-existent property name // Then it should return false +test("returns false when object does not contain the property", () => { + const obj = { a: 1, b: 2 }; + expect(contains(obj, "c")).toBe(false); +}); // Given invalid parameters like an array // When passed to contains // Then it should return false or throw an error +test("returns false for invalid parameters like an array", () => { + expect(contains([], "a")).toBe(false); + expect(contains(null, "a")).toBe(false); + expect(contains(123, "a")).toBe(false); + expect(contains("hello", "a")).toBe(false); +}); From 03c6296dec0dae24098fd15bf046e023b835d1c0 Mon Sep 17 00:00:00 2001 From: ChinweP Date: Wed, 29 Jul 2026 19:45:14 +0100 Subject: [PATCH 10/11] Fix invert function and add tests for key-value swapping --- Sprint-2/interpret/invert.js | 26 +++++++++++++++++++++----- Sprint-2/interpret/invert.test.js | 17 +++++++++++++++++ 2 files changed, 38 insertions(+), 5 deletions(-) create mode 100644 Sprint-2/interpret/invert.test.js diff --git a/Sprint-2/interpret/invert.js b/Sprint-2/interpret/invert.js index bb353fb1f..48466c281 100644 --- a/Sprint-2/interpret/invert.js +++ b/Sprint-2/interpret/invert.js @@ -5,7 +5,7 @@ // 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 = {}; @@ -15,15 +15,31 @@ function invert(obj) { return invertedObj; } + */ -// a) What is the current return value when invert is called with { a : 1 } +// a) What is the current return value when invert is called with { a : 1 } - The current return value is { key: 1} -// b) What is the current return value when invert is called with { a: 1, b: 2 } +// b) What is the current return value when invert is called with { a: 1, b: 2 } - The current return value is { key: 2} -// c) What is the target return value when invert is called with {a : 1, b: 2} +// c) What is the target return value when invert is called with {a : 1, b: 2} - The target return value is { "1": "a", "2": "b" } // c) What does Object.entries return? Why is it needed in this program? +/* Object.entries(obj) returns an array of [key, value] pairs. Example: Object.entries({ a:1, b:2 }) returns [["a", 1], ["b", 2]]. +It is needed because the loop "for (const [key, value] of Object.entries(obj))" let's each pair destructure easily. +*/ -// d) Explain why the current return value is different from the target output +// d) Explain why the current return value is different from the target output - It is because this line "invertedObj.key = value;" uses literal string "key instead of the variable key" which is a wrong property name, +// instead of this "invertedObj[value] = key;" // e) Fix the implementation of invert (and write tests to prove it's fixed!) +function invert(obj) { + const invertedObj = {}; + + for (const [key, value] of Object.entries(obj)) { + invertedObj[value] = key; + } + + return invertedObj; +} + +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..f8643a570 --- /dev/null +++ b/Sprint-2/interpret/invert.test.js @@ -0,0 +1,17 @@ +const invert = require("./invert.js"); + +test("inverts a single key-value pair", () => { + expect(invert({ a: 1 })).toEqual({ 1: "a" }); +}); + +test("inverts multiple key-value pairs", () => { + expect(invert({ a: 1, b: 2 })).toEqual({ 1: "a", 2: "b" }); +}); + +test("handles empty objects", () => { + expect(invert({})).toEqual({}); +}); + +test("handles string values", () => { + expect(invert({ x: "hello" })).toEqual({ hello: "x" }); +}); From 80160392edb10ff928d254014f2b510d2cba2112 Mon Sep 17 00:00:00 2001 From: ChinweP Date: Fri, 31 Jul 2026 23:19:20 +0100 Subject: [PATCH 11/11] =?UTF-8?q?Sprint=202=20submission=20=E2=80=94=20cle?= =?UTF-8?q?an=20branch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sprint-2/Sprint-2/debug/address.js | 28 +++++++++ Sprint-2/Sprint-2/debug/author.js | 34 ++++++++++ Sprint-2/Sprint-2/debug/recipe.js | 38 ++++++++++++ Sprint-2/Sprint-2/implement/contains.js | 10 +++ Sprint-2/Sprint-2/implement/contains.test.js | 62 +++++++++++++++++++ Sprint-2/Sprint-2/implement/lookup.js | 5 ++ Sprint-2/Sprint-2/implement/lookup.test.js | 35 +++++++++++ Sprint-2/Sprint-2/implement/querystring.js | 16 +++++ .../Sprint-2/implement/querystring.test.js | 48 ++++++++++++++ Sprint-2/Sprint-2/implement/tally.js | 3 + Sprint-2/Sprint-2/implement/tally.test.js | 34 ++++++++++ Sprint-2/Sprint-2/interpret/invert.js | 45 ++++++++++++++ Sprint-2/Sprint-2/interpret/invert.test.js | 17 +++++ Sprint-2/Sprint-2/package.json | 15 +++++ Sprint-2/Sprint-2/readme.md | 36 +++++++++++ Sprint-2/Sprint-2/stretch/count-words.js | 28 +++++++++ Sprint-2/Sprint-2/stretch/mode.js | 36 +++++++++++ Sprint-2/Sprint-2/stretch/mode.test.js | 32 ++++++++++ Sprint-2/Sprint-2/stretch/till.js | 31 ++++++++++ 19 files changed, 553 insertions(+) create mode 100644 Sprint-2/Sprint-2/debug/address.js create mode 100644 Sprint-2/Sprint-2/debug/author.js create mode 100644 Sprint-2/Sprint-2/debug/recipe.js create mode 100644 Sprint-2/Sprint-2/implement/contains.js create mode 100644 Sprint-2/Sprint-2/implement/contains.test.js create mode 100644 Sprint-2/Sprint-2/implement/lookup.js create mode 100644 Sprint-2/Sprint-2/implement/lookup.test.js create mode 100644 Sprint-2/Sprint-2/implement/querystring.js create mode 100644 Sprint-2/Sprint-2/implement/querystring.test.js create mode 100644 Sprint-2/Sprint-2/implement/tally.js create mode 100644 Sprint-2/Sprint-2/implement/tally.test.js create mode 100644 Sprint-2/Sprint-2/interpret/invert.js create mode 100644 Sprint-2/Sprint-2/interpret/invert.test.js create mode 100644 Sprint-2/Sprint-2/package.json create mode 100644 Sprint-2/Sprint-2/readme.md create mode 100644 Sprint-2/Sprint-2/stretch/count-words.js create mode 100644 Sprint-2/Sprint-2/stretch/mode.js create mode 100644 Sprint-2/Sprint-2/stretch/mode.test.js create mode 100644 Sprint-2/Sprint-2/stretch/till.js diff --git a/Sprint-2/Sprint-2/debug/address.js b/Sprint-2/Sprint-2/debug/address.js new file mode 100644 index 000000000..a3f63cd92 --- /dev/null +++ b/Sprint-2/Sprint-2/debug/address.js @@ -0,0 +1,28 @@ +// 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", + city: "Manchester", + country: "England", + postcode: "XYZ 123", +}; + +console.log(`My house number is ${address[0]}`); */ + +// The line "address[0]" is wrong. Because address is an object not an array, so "address[0]" is undefined. The output will be "My house number is undefined". + +// Corrected code +const address = { + houseNumber: 42, + street: "Imaginary Road", + city: "Manchester", + country: "England", + postcode: "XYZ 123", +}; + +console.log(`My house number is ${address.houseNumber}`); \ No newline at end of file diff --git a/Sprint-2/Sprint-2/debug/author.js b/Sprint-2/Sprint-2/debug/author.js new file mode 100644 index 000000000..37a05131d --- /dev/null +++ b/Sprint-2/Sprint-2/debug/author.js @@ -0,0 +1,34 @@ +// 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 + +/* const author = { + firstName: "Zadie", + lastName: "Smith", + occupation: "writer", + age: 40, + alive: true, +}; + +for (const value of author) { + console.log(value); +} +*/ + +// Prediction and Explanation +/* The line "for (const value of author) {console.log(value);}" will not work because for-of is used only for iterable things, e.g: arrays, strings, maps, sets. +But author is a plain object (created with {}), and plain objects are not iterable. So Javascript throws: TypeError: author is not iterable because objects don't have a natural order to loop through. */ + +// Corrected code +const author = { + firstName: "Zadie", + lastName: "Smith", + occupation: "writer", + age: 40, + alive: true, +}; + +for (const value of Object.values(author)) { + console.log(value); +} diff --git a/Sprint-2/Sprint-2/debug/recipe.js b/Sprint-2/Sprint-2/debug/recipe.js new file mode 100644 index 000000000..ab5c9d0cd --- /dev/null +++ b/Sprint-2/Sprint-2/debug/recipe.js @@ -0,0 +1,38 @@ +// 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, + ingredients: ["olive oil", "tomatoes", "salt", "pepper"], +}; + +console.log(`${recipe.title} serves ${recipe.serves} + ingredients: +${recipe}`); +*/ + +// Prediction and Explanation +/* +The line inside the template string: ${recipe} does not print the ingredients. +Instead, JavaScript converts the entire recipe object into a string, which becomes "[object Object]". This happens +because plain objects {} are not automatically converted into readable text. The program should print each ingredient on a new line, but the current code +never loops through recipe.ingredients, so nothing is printed correctly. +*/ + +// Corrected code +const recipe = { + title: "bruschetta", + serves: 2, + ingredients: ["olive oil", "tomatoes", "salt", "pepper"], +}; + +console.log(`${recipe.title} serves ${recipe.serves} +ingredients:`); + +for (const ingredient of recipe.ingredients) { + console.log(ingredient); +} diff --git a/Sprint-2/Sprint-2/implement/contains.js b/Sprint-2/Sprint-2/implement/contains.js new file mode 100644 index 000000000..05f18e140 --- /dev/null +++ b/Sprint-2/Sprint-2/implement/contains.js @@ -0,0 +1,10 @@ +function contains(obj, prop) { + if (typeof obj !== "object" || obj === null || Array.isArray(obj)) { + return false; + } + + return obj.hasOwnProperty(prop); +} + +module.exports = contains; + diff --git a/Sprint-2/Sprint-2/implement/contains.test.js b/Sprint-2/Sprint-2/implement/contains.test.js new file mode 100644 index 000000000..9a232aa56 --- /dev/null +++ b/Sprint-2/Sprint-2/implement/contains.test.js @@ -0,0 +1,62 @@ +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' +*/ + +// 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("returns true when object contains the property", () => { + const obj = { a: 1, b: 2 }; + expect(contains(obj, "a")).toBe(true); +}); + +test("returns false when object does not contain the property", () => { + const obj = { a: 1, b: 2 }; + expect(contains(obj, "c")).toBe(false); +}); + + +// 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({}, "a")).toBe(false); +}); + +// Given an object with properties +// When passed to contains with an existing property name +// Then it should return true +test("returns true when object contains the property", () => { + const obj = { a: 1, b: 2 }; + expect(contains(obj, "a")).toBe(true); +}); + +// Given an object with properties +// When passed to contains with a non-existent property name +// Then it should return false +test("returns false when object does not contain the property", () => { + const obj = { a: 1, b: 2 }; + expect(contains(obj, "c")).toBe(false); +}); + +// Given invalid parameters like an array +// When passed to contains +// Then it should return false or throw an error +test("returns false for invalid parameters like an array", () => { + expect(contains([], "a")).toBe(false); + expect(contains(null, "a")).toBe(false); + expect(contains(123, "a")).toBe(false); + expect(contains("hello", "a")).toBe(false); +}); diff --git a/Sprint-2/Sprint-2/implement/lookup.js b/Sprint-2/Sprint-2/implement/lookup.js new file mode 100644 index 000000000..a6746e07f --- /dev/null +++ b/Sprint-2/Sprint-2/implement/lookup.js @@ -0,0 +1,5 @@ +function createLookup() { + // implementation here +} + +module.exports = createLookup; diff --git a/Sprint-2/Sprint-2/implement/lookup.test.js b/Sprint-2/Sprint-2/implement/lookup.test.js new file mode 100644 index 000000000..547e06c5a --- /dev/null +++ b/Sprint-2/Sprint-2/implement/lookup.test.js @@ -0,0 +1,35 @@ +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' + } +*/ diff --git a/Sprint-2/Sprint-2/implement/querystring.js b/Sprint-2/Sprint-2/implement/querystring.js new file mode 100644 index 000000000..45ec4e5f3 --- /dev/null +++ b/Sprint-2/Sprint-2/implement/querystring.js @@ -0,0 +1,16 @@ +function parseQueryString(queryString) { + const queryParams = {}; + if (queryString.length === 0) { + return queryParams; + } + const keyValuePairs = queryString.split("&"); + + for (const pair of keyValuePairs) { + const [key, value] = pair.split("="); + queryParams[key] = value; + } + + return queryParams; +} + +module.exports = parseQueryString; diff --git a/Sprint-2/Sprint-2/implement/querystring.test.js b/Sprint-2/Sprint-2/implement/querystring.test.js new file mode 100644 index 000000000..328b8df61 --- /dev/null +++ b/Sprint-2/Sprint-2/implement/querystring.test.js @@ -0,0 +1,48 @@ +// In the prep, we implemented a function to parse query strings. +// Unfortunately, it contains several bugs! +// 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") + +test("should parse values containing '='", () => { + expect(parseQueryString("equation=a=b-2")).toEqual({ + equation: "a=b-2", + }); +}); + +test("should ignore empty key-value pairs", () => { + expect(parseQueryString("key1=value1&&key2=value2&")).toEqual({ + key1: "value1", + key2: "value2", + }); +}); + +test("should accept empty string as key or as value", () => { + expect(parseQueryString("=value")).toEqual({ "": "value" }); + expect(parseQueryString("key")).toEqual({ key: "" }); + expect(parseQueryString("key=")).toEqual({ key: "" }); + expect(parseQueryString("=")).toEqual({ "": "" }); +}); + +test("should decode percent-encoded characters", () => { + expect(parseQueryString("%24half=1%2F2")).toEqual({ + $half: "1/2", + }); +}); + +test("should replace '+' by ' '", () => { + expect(parseQueryString("full+name=John+Doe")).toEqual({ + "full name": "John Doe", + }); +}); + +// Stretch exercise: Handling query strings that contain identical keys + +// Delete this test if you are not working on this optional case +test("should store values of a key in an array when the key has 2 or more values", () => { + expect(parseQueryString("key=value1&key=value2&key=value3&foo=bar")).toEqual({ + key: ["value1", "value2", "value3"], + foo: "bar", + }); +}); diff --git a/Sprint-2/Sprint-2/implement/tally.js b/Sprint-2/Sprint-2/implement/tally.js new file mode 100644 index 000000000..f47321812 --- /dev/null +++ b/Sprint-2/Sprint-2/implement/tally.js @@ -0,0 +1,3 @@ +function tally() {} + +module.exports = tally; diff --git a/Sprint-2/Sprint-2/implement/tally.test.js b/Sprint-2/Sprint-2/implement/tally.test.js new file mode 100644 index 000000000..2ceffa8dd --- /dev/null +++ b/Sprint-2/Sprint-2/implement/tally.test.js @@ -0,0 +1,34 @@ +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 +// When passed an array of items +// Then it should return an object containing the count for each unique item + +// 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"); + +// Given an array with duplicate items +// When passed to tally +// Then it should return counts for each unique item + +// Given an invalid input like a string +// When passed to tally +// Then it should throw an error diff --git a/Sprint-2/Sprint-2/interpret/invert.js b/Sprint-2/Sprint-2/interpret/invert.js new file mode 100644 index 000000000..48466c281 --- /dev/null +++ b/Sprint-2/Sprint-2/interpret/invert.js @@ -0,0 +1,45 @@ +// 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; + } + + return invertedObj; +} + */ + +// a) What is the current return value when invert is called with { a : 1 } - The current return value is { key: 1} + +// b) What is the current return value when invert is called with { a: 1, b: 2 } - The current return value is { key: 2} + +// c) What is the target return value when invert is called with {a : 1, b: 2} - The target return value is { "1": "a", "2": "b" } + +// c) What does Object.entries return? Why is it needed in this program? +/* Object.entries(obj) returns an array of [key, value] pairs. Example: Object.entries({ a:1, b:2 }) returns [["a", 1], ["b", 2]]. +It is needed because the loop "for (const [key, value] of Object.entries(obj))" let's each pair destructure easily. +*/ + +// d) Explain why the current return value is different from the target output - It is because this line "invertedObj.key = value;" uses literal string "key instead of the variable key" which is a wrong property name, +// instead of this "invertedObj[value] = key;" + +// e) Fix the implementation of invert (and write tests to prove it's fixed!) +function invert(obj) { + const invertedObj = {}; + + for (const [key, value] of Object.entries(obj)) { + invertedObj[value] = key; + } + + return invertedObj; +} + +module.exports = invert; diff --git a/Sprint-2/Sprint-2/interpret/invert.test.js b/Sprint-2/Sprint-2/interpret/invert.test.js new file mode 100644 index 000000000..f8643a570 --- /dev/null +++ b/Sprint-2/Sprint-2/interpret/invert.test.js @@ -0,0 +1,17 @@ +const invert = require("./invert.js"); + +test("inverts a single key-value pair", () => { + expect(invert({ a: 1 })).toEqual({ 1: "a" }); +}); + +test("inverts multiple key-value pairs", () => { + expect(invert({ a: 1, b: 2 })).toEqual({ 1: "a", 2: "b" }); +}); + +test("handles empty objects", () => { + expect(invert({})).toEqual({}); +}); + +test("handles string values", () => { + expect(invert({ x: "hello" })).toEqual({ hello: "x" }); +}); diff --git a/Sprint-2/Sprint-2/package.json b/Sprint-2/Sprint-2/package.json new file mode 100644 index 000000000..80c16780f --- /dev/null +++ b/Sprint-2/Sprint-2/package.json @@ -0,0 +1,15 @@ +{ + "name": "sprint-2", + "version": "1.0.0", + "description": "This README will guide you through the different sections for this sprint.", + "main": "", + "scripts": { + "test": "jest" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "jest": "^29.7.0" + } +} diff --git a/Sprint-2/Sprint-2/readme.md b/Sprint-2/Sprint-2/readme.md new file mode 100644 index 000000000..81ab69550 --- /dev/null +++ b/Sprint-2/Sprint-2/readme.md @@ -0,0 +1,36 @@ +# 🧭 Guide to Sprint 2 exercises + +This README will guide you through the different sections for this sprint. + +## Setup + +Make sure your terminal is in the `Sprint-2` directory of this repository. + +Run the command `npm install` to install any dependencies you need. + +## πŸ›Β Debug + +Each of the files in `debug` contains a πŸ›. Predict and explain first and then run the code with node to check your prediction. Fix any bugs that appear in the code. + +## πŸ”¨ Implement + +In this section, you will have a short set of requirements about a function. You will need to implement a function based off this set of requirements. Make sure you check your function works for a number of different inputs. + +Here is a recommended order: + +1. `contains.test.js` +2. `lookup.test.js` +3. `tally.test.js` +4. `querystring.test.js` + +## Interpret + +In these tasks, you have to interpret a slightly larger program with some syntax / operators / functions that may be unfamiliar. +You must use documentation to make sense of anything unfamiliar - learning how to look things up this way is a fundamental part of being a developer! +You can also use `console.log` to check the value of different variables in the code. + +Once you've completed all these tasks, raise a PR with your work so far. Don't wait to complete your coursework before completing the PR. + +## Stretch πŸ’ͺ + +Try the stretch tasks after you've completed the other sections. diff --git a/Sprint-2/Sprint-2/stretch/count-words.js b/Sprint-2/Sprint-2/stretch/count-words.js new file mode 100644 index 000000000..8e85d19d7 --- /dev/null +++ b/Sprint-2/Sprint-2/stretch/count-words.js @@ -0,0 +1,28 @@ +/* + Count the number of times a word appears in a given string. + + Write a function called countWords that + - takes a string as an argument + - returns an object where + - the keys are the words from the string and + - the values are the number of times the word appears in the string + + Example + If we call countWords like this: + + countWords("you and me and you") then the target output is { you: 2, and: 2, me: 1 } + + To complete this exercise you should understand + - Strings and string manipulation + - Loops + - Comparison inside if statements + - Setting values on an object + +## Advanced challenges + +1. Remove all of the punctuation (e.g. ".", ",", "!", "?") to tidy up the results + +2. Ignore the case of the words to find more unique words. e.g. (A === a, Hello === hello) + +3. Order the results to find out which word is the most common in the input +*/ diff --git a/Sprint-2/Sprint-2/stretch/mode.js b/Sprint-2/Sprint-2/stretch/mode.js new file mode 100644 index 000000000..3f7609d79 --- /dev/null +++ b/Sprint-2/Sprint-2/stretch/mode.js @@ -0,0 +1,36 @@ +// You are given an implementation of calculateMode + +// calculateMode's implementation can be broken down into two stages: + +// Stage 1. One part of the code tracks the frequency of each value +// Stage 2. The other part finds the value with the highest frequency + +// refactor calculateMode by splitting up the code +// into smaller functions using the stages above + +function calculateMode(list) { + // track frequency of each value + let freqs = new Map(); + + for (let num of list) { + if (typeof num !== "number") { + continue; + } + + freqs.set(num, (freqs.get(num) || 0) + 1); + } + + // Find the value with the highest frequency + let maxFreq = 0; + let mode; + for (let [num, freq] of freqs) { + if (freq > maxFreq) { + mode = num; + maxFreq = freq; + } + } + + return maxFreq === 0 ? NaN : mode; +} + +module.exports = calculateMode; diff --git a/Sprint-2/Sprint-2/stretch/mode.test.js b/Sprint-2/Sprint-2/stretch/mode.test.js new file mode 100644 index 000000000..ca33c28a3 --- /dev/null +++ b/Sprint-2/Sprint-2/stretch/mode.test.js @@ -0,0 +1,32 @@ +const calculateMode = require("./mode.js"); + +// Acceptance criteria for calculateMode function + +// Given an array of numbers +// When calculateMode is called on the array +// Then it should return the number that appears most frequently in the array + +// Example: +// Given [2,4,1,2,3,2,1] +// When calculateMode is called on [2,4,1,2,3,2,1] +// Then it should return 2 */ + +describe("calculateMode()", () => { + test("returns the most frequent number in an array", () => { + const nums = [2, 4, 1, 2, 3, 2, 1]; + + expect(calculateMode(nums)).toEqual(2); + }); + + test("returns the first mode in case of multiple modes", () => { + const nums = [1, 2, 2, 3, 3]; + + expect(calculateMode(nums)).toEqual(2); + }); + + test("ignores non-number values", () => { + const nums = [1, 3, "2", 2, 3, null]; + + expect(calculateMode(nums)).toEqual(3); + }); +}); diff --git a/Sprint-2/Sprint-2/stretch/till.js b/Sprint-2/Sprint-2/stretch/till.js new file mode 100644 index 000000000..6a08532e7 --- /dev/null +++ b/Sprint-2/Sprint-2/stretch/till.js @@ -0,0 +1,31 @@ +// totalTill takes an object representing coins in a till + +// Given an object of coins +// When this till object is passed to totalTill +// Then it should return the total amount in pounds + +function totalTill(till) { + let total = 0; + + for (const [coin, quantity] of Object.entries(till)) { + total += coin * quantity; + } + + return `Β£${total / 100}`; +} + +const till = { + "1p": 10, + "5p": 6, + "50p": 4, + "20p": 10, +}; +const totalAmount = totalTill(till); + +// a) What is the target output when totalTill is called with the till object + +// b) Why do we need to use Object.entries inside the for...of loop in this function? + +// c) What does coin * quantity evaluate to inside the for...of loop? + +// d) Write a test for this function to check it works and then fix the implementation of totalTill