From 3d7370b3f50afdcbe12c569f84c9c1130ff0664c Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Sun, 22 Feb 2026 16:41:18 +0200 Subject: [PATCH 01/56] Implementation for median --- Sprint-1/fix/median.js | 22 +++++++++++++--------- Sprint-1/package-lock.json | 2 ++ 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/Sprint-1/fix/median.js b/Sprint-1/fix/median.js index b22590bc6..3a19194bc 100644 --- a/Sprint-1/fix/median.js +++ b/Sprint-1/fix/median.js @@ -1,14 +1,18 @@ -// Fix this implementation -// Start by running the tests for this function -// If you're in the Sprint-1 directory, you can run `npm test -- fix` to run the tests in the fix directory +function calculateMedian(list) { + if (!Array.isArray(list)) return null; -// Hint: Please consider scenarios when 'list' doesn't have numbers (the function is expected to return null) -// or 'list' has mixed values (the function is expected to sort only numbers). + const numericValues = list.filter((val) => typeof val === "number"); + if (numericValues.length === 0) return null; -function calculateMedian(list) { - const middleIndex = Math.floor(list.length / 2); - const median = list.splice(middleIndex, 1)[0]; + const sortedList = numericValues.toSorted((a, b) => a - b); + const middleIndex = Math.floor(sortedList.length / 2); + let median; + + if (sortedList.length % 2 === 1) { + median = sortedList[middleIndex]; + } else { + median = (sortedList[middleIndex - 1] + sortedList[middleIndex]) / 2; + } return median; } - module.exports = calculateMedian; diff --git a/Sprint-1/package-lock.json b/Sprint-1/package-lock.json index 83e427d0b..b52480af5 100644 --- a/Sprint-1/package-lock.json +++ b/Sprint-1/package-lock.json @@ -56,6 +56,7 @@ "integrity": "sha512-Oixnb+DzmRT30qu9d3tJSQkxuygWm32DFykT4bRoORPa9hZ/L4KhVB/XiRm6KG+roIEM7DBQlmg27kw2HZkdZg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.25.7", @@ -1368,6 +1369,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001663", "electron-to-chromium": "^1.5.28", From fdbd966123fec8ce28d631198694ff7524351410 Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Sun, 22 Feb 2026 18:10:13 +0200 Subject: [PATCH 02/56] Test for empty array --- Sprint-1/implement/dedupe.test.js | 29 ++++------------------------- 1 file changed, 4 insertions(+), 25 deletions(-) diff --git a/Sprint-1/implement/dedupe.test.js b/Sprint-1/implement/dedupe.test.js index 23e0f8638..5ff1b4532 100644 --- a/Sprint-1/implement/dedupe.test.js +++ b/Sprint-1/implement/dedupe.test.js @@ -1,27 +1,6 @@ const dedupe = require("./dedupe.js"); -/* -Dedupe Array -📖 Dedupe means **deduplicate** - -In this kata, you will need to deduplicate the elements of an array - -E.g. dedupe(['a','a','a','b','b','c']) target output: ['a','b','c'] -E.g. dedupe([5, 1, 1, 2, 3, 2, 5, 8]) target output: [5, 1, 2, 3, 8] -E.g. dedupe([1, 2, 1]) target output: [1, 2] -*/ - -// Acceptance Criteria: - -// Given an empty array -// When passed to the dedupe function -// Then it should return an empty array -test.todo("given an empty array, it returns an empty array"); - -// Given an array with no duplicates -// When passed to the dedupe function -// Then it should return a copy of the original array - -// Given an array with strings or numbers -// When passed to the dedupe function -// Then it should remove the duplicate values, preserving the first occurence of each element +// Case 1: Return an empty array when input is [] +test("given an empty array, it returns an empty array", () => { + expect(dedupe([])).toEqual([]); +}); From a28e9041f925c037963726aec2f22ea1d9762078 Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Sun, 22 Feb 2026 18:11:04 +0200 Subject: [PATCH 03/56] Test for array without duplicates --- Sprint-1/implement/dedupe.test.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Sprint-1/implement/dedupe.test.js b/Sprint-1/implement/dedupe.test.js index 5ff1b4532..6b7aa49dd 100644 --- a/Sprint-1/implement/dedupe.test.js +++ b/Sprint-1/implement/dedupe.test.js @@ -4,3 +4,19 @@ const dedupe = require("./dedupe.js"); test("given an empty array, it returns an empty array", () => { expect(dedupe([])).toEqual([]); }); + +// Case 2: Return original array where there are no duplicates +test("given an array without duplicates, it returns the original", () => { + const noDuplicates = [ + { input: [3, 1, 2], expected: [3, 1, 2] }, + { input: [5, 1, 3, 4, 2], expected: [5, 1, 3, 4, 2] }, + { input: [4, 2, 1, 3], expected: [4, 2, 1, 3] }, + { input: [6, 1, 5, 3, 2, 4], expected: [6, 1, 5, 3, 2, 4] }, + { input: [110, 20, 0], expected: [110, 20, 0] }, + { input: [6, 5, 2, 12, 14], expected: [6, 5, 2, 12, 14] }, + ]; + + noDuplicates.forEach(({ input, expected }) => { + expect(dedupe(input)).toEqual(expected); + }); +}); From 06339fc07627a0e162a484a322de8498c9df374b Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Mon, 23 Feb 2026 00:20:00 +0200 Subject: [PATCH 04/56] Implement dedupe --- Sprint-1/implement/dedupe.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Sprint-1/implement/dedupe.js b/Sprint-1/implement/dedupe.js index 781e8718a..de2736e95 100644 --- a/Sprint-1/implement/dedupe.js +++ b/Sprint-1/implement/dedupe.js @@ -1 +1,10 @@ -function dedupe() {} +function dedupe(list) { + if (!Array.isArray(list)) + throw new Error("Please input an array e.g. [1, 3, 6]"); + + if (list.length === 0) return []; + + return [...new Set(list)]; +} + +module.exports = dedupe; From 4426ed9c0cd896a345b5e1469b37a899424b851b Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Wed, 25 Feb 2026 02:19:17 +0200 Subject: [PATCH 05/56] Testing for dedupe --- Sprint-1/implement/dedupe.test.js | 70 ++++++++++++++++++++++++++++--- 1 file changed, 65 insertions(+), 5 deletions(-) diff --git a/Sprint-1/implement/dedupe.test.js b/Sprint-1/implement/dedupe.test.js index 6b7aa49dd..b5848df28 100644 --- a/Sprint-1/implement/dedupe.test.js +++ b/Sprint-1/implement/dedupe.test.js @@ -8,15 +8,75 @@ test("given an empty array, it returns an empty array", () => { // Case 2: Return original array where there are no duplicates test("given an array without duplicates, it returns the original", () => { const noDuplicates = [ - { input: [3, 1, 2], expected: [3, 1, 2] }, - { input: [5, 1, 3, 4, 2], expected: [5, 1, 3, 4, 2] }, { input: [4, 2, 1, 3], expected: [4, 2, 1, 3] }, - { input: [6, 1, 5, 3, 2, 4], expected: [6, 1, 5, 3, 2, 4] }, - { input: [110, 20, 0], expected: [110, 20, 0] }, - { input: [6, 5, 2, 12, 14], expected: [6, 5, 2, 12, 14] }, + { input: [3, 1, 2], expected: [3, 1, 2] }, + { input: ["5", "1", "3", "4", "2"], expected: ["5", "1", "3", "4", "2"] }, + { + input: ["6", "1", "5", "3", "2", "4"], + expected: ["6", "1", "5", "3", "2", "4"], + }, + { input: [110, "20", 0], expected: [110, "20", 0] }, + { + input: [6, "five", 2, "apple", 14], + expected: [6, "five", 2, "apple", 14], + }, ]; noDuplicates.forEach(({ input, expected }) => { expect(dedupe(input)).toEqual(expected); }); }); + +// Case 3: Return array with duplicates removed when input has duplicates +test("given an array with duplicates, it returns unique items", () => { + const withDuplicates = [ + { input: [3, 1, 1, 2], expected: [3, 1, 2] }, + { input: [5, 1, 5, 3, 5, 4, 2], expected: [5, 1, 3, 4, 2] }, + { input: ["4", "2", "4", "1", "2", "3"], expected: ["4", "2", "1", "3"] }, + { + input: ["six", "one", "5", "six", "3", "six", "2", "5", "4"], + expected: ["six", "one", "5", "3", "2", "4"], + }, + { input: [110, "20", 110, "zero", "zero"], expected: [110, "20", "zero"] }, + { input: [6, 5, 2, 6, 5, 2], expected: [6, 5, 2] }, + ]; + + withDuplicates.forEach(({ input, expected }) => { + expect(dedupe(input)).toEqual(expected); + }); +}); + +// Case 4: Throw an error if an array isn't used +test("should throw an error if the input is not an array", () => { + const notArrays = [ + "hello world", + "", + null, + -5, + undefined, + {}, + Infinity, + true, + ]; + + notArrays.forEach((notArray) => { + expect(() => { + dedupe(notArray); + }).toThrow(); + }); +}); + +// Case 5: Don't modify the input array +test("doesn't modify the input array", () => { + const inputArrays = [ + [3, 1, 1, 2], + ["4", "2", "4", "1", "2", "3"], + [6, "five", 2, "apple", 14], + [110, undefined, Infinity, {}, "zero", true], + ]; + inputArrays.forEach((inputArray) => { + const original = [...inputArray]; + dedupe(inputArray); + expect(inputArray).toEqual(original); + }); +}); From 4b1ac0d7f29ea9eefd08f20bfe4032fec0b86769 Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Wed, 25 Feb 2026 02:59:51 +0200 Subject: [PATCH 06/56] Refactor test --- Sprint-1/implement/dedupe.test.js | 166 ++++++++++++++++++------------ 1 file changed, 98 insertions(+), 68 deletions(-) diff --git a/Sprint-1/implement/dedupe.test.js b/Sprint-1/implement/dedupe.test.js index b5848df28..d909e43d8 100644 --- a/Sprint-1/implement/dedupe.test.js +++ b/Sprint-1/implement/dedupe.test.js @@ -1,82 +1,112 @@ const dedupe = require("./dedupe.js"); -// Case 1: Return an empty array when input is [] -test("given an empty array, it returns an empty array", () => { - expect(dedupe([])).toEqual([]); -}); +describe("dedupe", () => { + // Case 1: Return an empty array when input is [] + test("given an empty array, it returns an empty array", () => { + expect(dedupe([])).toEqual([]); + }); -// Case 2: Return original array where there are no duplicates -test("given an array without duplicates, it returns the original", () => { - const noDuplicates = [ - { input: [4, 2, 1, 3], expected: [4, 2, 1, 3] }, - { input: [3, 1, 2], expected: [3, 1, 2] }, - { input: ["5", "1", "3", "4", "2"], expected: ["5", "1", "3", "4", "2"] }, - { - input: ["6", "1", "5", "3", "2", "4"], - expected: ["6", "1", "5", "3", "2", "4"], - }, - { input: [110, "20", 0], expected: [110, "20", 0] }, - { - input: [6, "five", 2, "apple", 14], - expected: [6, "five", 2, "apple", 14], - }, - ]; + // Case 2: Return original array where there are no duplicates + test("given an array without duplicates, it returns the original", () => { + const noDuplicates = [ + { input: [4, 2, 1, 3], expected: [4, 2, 1, 3] }, + { input: [3, 1, 2], expected: [3, 1, 2] }, + { input: ["5", "1", "3", "4", "2"], expected: ["5", "1", "3", "4", "2"] }, + { + input: ["6", "1", "5", "3", "2", "4"], + expected: ["6", "1", "5", "3", "2", "4"], + }, + { input: [110, "20", 0], expected: [110, "20", 0] }, + { + input: [6, "five", 2, "apple", 14], + expected: [6, "five", 2, "apple", 14], + }, + ]; - noDuplicates.forEach(({ input, expected }) => { - expect(dedupe(input)).toEqual(expected); + noDuplicates.forEach(({ input, expected }) => { + try { + expect(dedupe(input)).toEqual(expected); + } catch (error) { + throw new Error( + `Failed to return the original [${input}] when no duplicates are present: ${error.message}` + ); + } + }); }); -}); -// Case 3: Return array with duplicates removed when input has duplicates -test("given an array with duplicates, it returns unique items", () => { - const withDuplicates = [ - { input: [3, 1, 1, 2], expected: [3, 1, 2] }, - { input: [5, 1, 5, 3, 5, 4, 2], expected: [5, 1, 3, 4, 2] }, - { input: ["4", "2", "4", "1", "2", "3"], expected: ["4", "2", "1", "3"] }, - { - input: ["six", "one", "5", "six", "3", "six", "2", "5", "4"], - expected: ["six", "one", "5", "3", "2", "4"], - }, - { input: [110, "20", 110, "zero", "zero"], expected: [110, "20", "zero"] }, - { input: [6, 5, 2, 6, 5, 2], expected: [6, 5, 2] }, - ]; + // Case 3: Return array with duplicates removed when input has duplicates + test("given an array with duplicates, it returns unique items", () => { + const withDuplicates = [ + { input: [3, 1, 1, 2], expected: [3, 1, 2] }, + { input: [5, 1, 5, 3, 5, 4, 2], expected: [5, 1, 3, 4, 2] }, + { input: ["4", "2", "4", "1", "2", "3"], expected: ["4", "2", "1", "3"] }, + { + input: ["six", "one", "5", "six", "3", "six", "2", "5", "4"], + expected: ["six", "one", "5", "3", "2", "4"], + }, + { + input: [110, "20", 110, "zero", "zero"], + expected: [110, "20", "zero"], + }, + { input: [6, 5, 2, 6, 5, 2], expected: [6, 5, 2] }, + ]; - withDuplicates.forEach(({ input, expected }) => { - expect(dedupe(input)).toEqual(expected); + withDuplicates.forEach(({ input, expected }) => { + try { + expect(dedupe(input)).toEqual(expected); + } catch (error) { + throw new Error( + `Failed to remove duplicates from input [${input}]: ${error.message}` + ); + } + }); }); -}); -// Case 4: Throw an error if an array isn't used -test("should throw an error if the input is not an array", () => { - const notArrays = [ - "hello world", - "", - null, - -5, - undefined, - {}, - Infinity, - true, - ]; + // Case 4: Throw an error if an array isn't used + test("given a non-array input, it throws an error", () => { + const notArrays = [ + "hello world", + "", + null, + -5, + undefined, + {}, + Infinity, + true, + NaN, + ]; - notArrays.forEach((notArray) => { - expect(() => { - dedupe(notArray); - }).toThrow(); + notArrays.forEach((notArray) => { + try { + expect(() => { + dedupe(notArray); + }).toThrow(); + } catch (error) { + throw new Error( + `Failed to throw an error for ${JSON.stringify(notArray)} (${typeof notArray}): ${error.message}` + ); + } + }); }); -}); -// Case 5: Don't modify the input array -test("doesn't modify the input array", () => { - const inputArrays = [ - [3, 1, 1, 2], - ["4", "2", "4", "1", "2", "3"], - [6, "five", 2, "apple", 14], - [110, undefined, Infinity, {}, "zero", true], - ]; - inputArrays.forEach((inputArray) => { - const original = [...inputArray]; - dedupe(inputArray); - expect(inputArray).toEqual(original); + // Case 5: Don't modify the input array + test("doesn't modify the input array", () => { + const inputArrays = [ + [3, 1, 1, 2], + ["4", "2", "4", "1", "2", "3"], + [6, "five", 2, "apple", 14], + [110, undefined, Infinity, {}, "zero", true], + ]; + inputArrays.forEach((inputArray) => { + try { + const original = [...inputArray]; + dedupe(inputArray); + expect(inputArray).toEqual(original); + } catch (error) { + throw new Error( + `Failed to keep the original input array [${inputArray}] unmutated: ${error.message}` + ); + } + }); }); }); From b6c08073b354800a60cf101911b8b4c95ffc2ef5 Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Wed, 25 Feb 2026 15:01:54 +0200 Subject: [PATCH 07/56] Testing for max --- Sprint-1/implement/max.test.js | 143 ++++++++++++++++++++++++++------- 1 file changed, 112 insertions(+), 31 deletions(-) diff --git a/Sprint-1/implement/max.test.js b/Sprint-1/implement/max.test.js index 82f18fd88..72a8324df 100644 --- a/Sprint-1/implement/max.test.js +++ b/Sprint-1/implement/max.test.js @@ -1,43 +1,124 @@ -/* Find the maximum element of an array of numbers +const findMax = require("./max.js"); -In this kata, you will need to implement a function that find the largest numerical element of an array. +describe("findMax", () => { + // Case 1: Given an empty array, it returns -Infinity + test("given an empty array, returns -Infinity", () => { + expect(findMax([])).toEqual(-Infinity); + }); -E.g. max([30, 50, 10, 40]), target output: 50 -E.g. max(['hey', 10, 'hi', 60, 10]), target output: 60 (sum ignores any non-numerical elements) + // Case 2: Given an array with one number, it returns that number + test("given an array with one number, it returns that number", () => { + const singleNumbers = [0, 1, 6, 10, 100, 545465, -999, -78]; -You should implement this function in max.js, and add tests for it in this file. + singleNumbers.forEach((num) => { + try { + expect(findMax(num)).toEqual(num); + } catch (error) { + throw new Error( + `Failed to return ${num} when given an array with a single number: ${error.message}` + ); + } + }); + }); -We have set things up already so that this file can see your function from the other file. -*/ + // Case 3: Given an array with both positive and negative numbers, it returns the largest number overall + test("given an array both positive and negative numbers, it returns the largest number", () => { + const posAndNegs = [ + { input: [3, -4], expected: 3 }, + { input: [1, 54, -5, -3], expected: 54 }, + { input: [-40, -2, -1, 3], expected: 3 }, + { input: [-6, -1, -5, -3, -2, -4], expected: -1 }, + { input: [785, 0, -999], expected: 785 }, + ]; -const findMax = require("./max.js"); + posAndNegs.forEach(({ input, expected }) => { + try { + expect(findMax(input)).toEqual(expected); + } catch (error) { + throw new Error( + `Failed to return the largest number ${expected} when given the array [${input}] containing positive and negative numbers: ${error.message}` + ); + } + }); + }); + + // Case 4: Given an array with just negative numbers, returns the closest one to zero + test("given an array with just negative numbers, it returns the number closest to zero", () => { + const negNums = [ + { input: [-1, -2, -3, -4, -5], expected: -1 }, + { input: [-5, -4, -3, -2, -1], expected: -1 }, + { input: [-10, -20, -20, -5, -5], expected: -5 }, + { input: [-100, -1000, -100, -57, -57], expected: -57 }, + { input: [-18, -98, -3, -16, -20000], expected: -3 }, + ]; -// Given an empty array -// 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"); + negNums.forEach(({ input, expected }) => { + try { + expect(findMax(input)).toEqual(expected); + } catch (error) { + throw new Error( + `Failed to return the number closest to zero ${expected} when given the array [${input}] containing only negative numbers: ${error.message}` + ); + } + }); + }); -// Given an array with one number -// When passed to the max function -// Then it should return that number + // Case 5: Given an array with decimal numbers returns the largest decimal number + test("given an array with decimal numbers, it returns the largest decimal number", () => { + const decimalNums = [ + { input: [1.5, 2.3, 3.7], expected: 3.7 }, + { input: [0.1, 0.2, 0.3], expected: 0.3 }, + { input: [-1.5, -2.3, -3.7], expected: -1.5 }, + { input: [-6.87, -0.3, -99], expected: -0.3 }, + { input: [-15, -23, 37], expected: 37 }, + ]; -// Given an array with both positive and negative numbers -// When passed to the max function -// Then it should return the largest number overall + decimalNums.forEach(({ input, expected }) => { + try { + expect(findMax(input)).toEqual(expected); + } catch (error) { + throw new Error( + `Failed to return the largest decimal number ${expected} when given the array [${input}]: ${error.message}` + ); + } + }); + }); -// Given an array with just negative numbers -// When passed to the max function -// Then it should return the closest one to zero + // Case 6: Given an array with non-number values, it returns the max and ignore non-numeric values + test("given an array with non-number values, it returns the max and ignore non-numeric values", () => { + const withNonNums = [ + { input: [1, 2, "hello", 3, null, 4, undefined, 5], expected: 5 }, + { input: ["a", "b", 0], expected: 0 }, + { input: [null, undefined, false, true, -Infinity, NaN, -100], expected: -100 }, + ]; -// Given an array with decimal numbers -// When passed to the max function -// Then it should return the largest decimal number + withNonNums.forEach(({ input, expected }) => { + try { + expect(findMax(input)).toEqual(expected); + } catch (error) { + throw new Error( + `Failed to return the expected value ${expected} when given the array [${input}]: ${error.message}` + ); + } + }); + }); -// Given an array with non-number values -// When passed to the max function -// Then it should return the max and ignore non-numeric values + // Case 7: Given an array with only non-number values, it returns the -Infinity + test("given an array with only non-number values, it returns the -Infinity", () => { + const onlyNonNums = [ + { input: ["a", "b", "c"], expected: -Infinity }, + { input: [null, undefined, false], expected: -Infinity }, + { input: [NaN, Infinity, -Infinity], expected: -Infinity }, + ]; -// 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 + onlyNonNums.forEach(({ input, expected }) => { + try { + expect(findMax(input)).toEqual(expected); + } catch (error) { + throw new Error( + `Failed to return the expected value ${expected} when given the array [${input}]: ${error.message}` + ); + } + }); + }); + }); \ No newline at end of file From aa0f82d3d02a1ef3e649604a8bdbb8adb1b18d35 Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Wed, 25 Feb 2026 18:06:21 +0200 Subject: [PATCH 08/56] Fix: correct case 2 input to array with single number, not just a single number (non array) --- Sprint-1/implement/max.test.js | 38 +++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/Sprint-1/implement/max.test.js b/Sprint-1/implement/max.test.js index 72a8324df..08f98de08 100644 --- a/Sprint-1/implement/max.test.js +++ b/Sprint-1/implement/max.test.js @@ -12,7 +12,8 @@ describe("findMax", () => { singleNumbers.forEach((num) => { try { - expect(findMax(num)).toEqual(num); + const expected = num; + expect(findMax([num])).toEqual(expected); } catch (error) { throw new Error( `Failed to return ${num} when given an array with a single number: ${error.message}` @@ -87,15 +88,18 @@ describe("findMax", () => { // Case 6: Given an array with non-number values, it returns the max and ignore non-numeric values test("given an array with non-number values, it returns the max and ignore non-numeric values", () => { const withNonNums = [ - { input: [1, 2, "hello", 3, null, 4, undefined, 5], expected: 5 }, - { input: ["a", "b", 0], expected: 0 }, - { input: [null, undefined, false, true, -Infinity, NaN, -100], expected: -100 }, + { input: [1, 2, "hello", 3, null, 4, undefined, 5], expected: 5 }, + { input: ["a", "b", 0], expected: 0 }, + { + input: [null, undefined, false, true, -Infinity, NaN, -100], + expected: -100, + }, ]; withNonNums.forEach(({ input, expected }) => { - try { - expect(findMax(input)).toEqual(expected); - } catch (error) { + try { + expect(findMax(input)).toEqual(expected); + } catch (error) { throw new Error( `Failed to return the expected value ${expected} when given the array [${input}]: ${error.message}` ); @@ -111,14 +115,14 @@ describe("findMax", () => { { input: [NaN, Infinity, -Infinity], expected: -Infinity }, ]; - onlyNonNums.forEach(({ input, expected }) => { - try { - expect(findMax(input)).toEqual(expected); - } catch (error) { - throw new Error( - `Failed to return the expected value ${expected} when given the array [${input}]: ${error.message}` - ); - } - }); + onlyNonNums.forEach(({ input, expected }) => { + try { + expect(findMax(input)).toEqual(expected); + } catch (error) { + throw new Error( + `Failed to return the expected value ${expected} when given the array [${input}]: ${error.message}` + ); + } }); - }); \ No newline at end of file + }); +}); From cc952dd8584f5babe6f00fee76077f25ce176ccf Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Wed, 25 Feb 2026 18:06:54 +0200 Subject: [PATCH 09/56] Implement findMax --- Sprint-1/implement/max.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Sprint-1/implement/max.js b/Sprint-1/implement/max.js index 6dd76378e..d3308a041 100644 --- a/Sprint-1/implement/max.js +++ b/Sprint-1/implement/max.js @@ -1,4 +1,7 @@ function findMax(elements) { + const numbersOnly = elements.filter((element) => Number.isFinite(element)); + + return numbersOnly.reduce((a, b) => Math.max(a, b), -Infinity); } module.exports = findMax; From 8596f7fada0bedc5d42a2ddae48fabbd3ae0ff07 Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Wed, 25 Feb 2026 23:05:58 +0200 Subject: [PATCH 10/56] Readability: remove unused code --- Sprint-1/implement/sum.test.js | 142 +++++++++++++++++++++++++++------ 1 file changed, 116 insertions(+), 26 deletions(-) diff --git a/Sprint-1/implement/sum.test.js b/Sprint-1/implement/sum.test.js index dd0a090ca..6372369f6 100644 --- a/Sprint-1/implement/sum.test.js +++ b/Sprint-1/implement/sum.test.js @@ -1,36 +1,126 @@ -/* Sum the numbers in an array +const sum = require("./sum.js"); -In this kata, you will need to implement a function that sums the numerical elements of an array +// Case 1: Given an empty array, it should return 0 +test("given an empty array, returns 0", () => { + expect(sum([])).toEqual(0); +}); -E.g. sum([10, 20, 30]), target output: 60 -E.g. sum(['hey', 10, 'hi', 60, 10]), target output: 80 (ignore any non-numerical elements) -*/ +// Case 2: Given an array with just one number, it should return that number +test("given an array with just one number, returns that number", () => { + try { + const singleNums = [0, 1, 5, 10, 100, -999, -78]; + singleNums.forEach((num) => { + expect(sum([num])).toEqual(num); + }); + } catch (error) { + throw new Error( + `Failed to return ${num} when given an array with just one number: ${error.message}` + ); + } +}); -const sum = require("./sum.js"); +// Case 3: Given an array containing negative numbers, it should return the correct total sum +test("given an array with negative numbers, returns the correct total sum", () => { + const withNegs = [ + { input: [10, -5], expected: 5 }, + { input: [1, 54, -5, -3], expected: 47 }, + { input: [-40, -2, -1, 3], expected: -40 }, + { input: [-6, -1, -5, -3, -2, -4], expected: -21 }, + { input: [785, 0, -999], expected: -214 }, + ]; + + withNegs.forEach(({ input, expected }) => { + try { + expect(sum(input)).toEqual(expected); + } catch (error) { + throw new Error( + `Failed to return the correct total sum ${expected} when given the array [${input}] containing negative numbers: ${error.message}` + ); + } + }); +}); + +// Case 4: Given an array with decimal/float numbers, it should return the correct total sum +test("given an array with decimal/float numbers, returns the correct total sum", () => { + const withFloats = [ + { input: [1.5, 2.7, 3.8], expected: 8 }, + { input: [-1.5, -2.7, -3.8], expected: -8 }, + { input: [1.5, -2.7, 3.8], expected: 2.6 }, + { input: [-1.5, 2.7, -3.8], expected: -2.6 }, + ]; -// Acceptance Criteria: + withFloats.forEach(({ input, expected }) => { + try { + expect(sum(input)).toEqual(expected); + } catch (error) { + throw new Error( + `Failed to return the correct total sum ${expected} when given the array [${input}] containing decimal/float numbers: ${error.message}` + ); + } + }); +}); -// Given an empty array -// When passed to the sum function -// Then it should return 0 -test.todo("given an empty array, returns 0") +// Case 5: Given an array containing non-number values, it should return the sum of the numerical elements and ignore non-numerical values +test("given an array with non-number values, it returns the sum of the numerical elements and ignores non-numerical values", () => { + const withNonNumbers = [ + { input: ["hey", 10, "hi", 60, 10], expected: 80 }, + { input: ["hello", "world"], expected: 0 }, + { input: [1, 2, "three", 4, "five"], expected: 7 }, + { input: [null, undefined, false, true], expected: 0 }, + { input: [1.5, "two", 3.5], expected: 5 }, + ]; -// Given an array with just one number -// When passed to the sum function -// Then it should return that number + withNonNumbers.forEach(({ input, expected }) => { + try { + expect(sum(input)).toEqual(expected); + } catch (error) { + throw new Error( + `Failed to return the correct sum ${expected} when given the array [${input}] containing non-number values: ${error.message}` + ); + } + }); +}); -// Given an array containing negative numbers -// When passed to the sum function -// Then it should still return the correct total sum +// Case 6: Given an array with only non-number values, it should return zero +test("given an array with only non-number values, it returns the least surprising value given how it behaves for all other inputs", () => { + const onlyNonNumbers = [ + { input: ["hello", "world"], expected: 0 }, + { input: [null, undefined, false, {}], expected: 0 }, + { input: ["a", "b", null, NaN], expected: 0 }, + { input: [Infinity, []], expected: 0 }, + ]; -// Given an array with decimal/float numbers -// When passed to the sum function -// Then it should return the correct total sum + onlyNonNumbers.forEach(({ input, expected }) => { + try { + expect(sum(input)).toEqual(expected); + } catch (error) { + throw new Error( + `Failed to return the least surprising value ${expected} when given the array [${input}] containing only non-number values: ${error.message}` + ); + } + }); +}); -// 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 +// Case 7: Given an array with non-number values, it should return the sum of the numerical elements and ignore non-numerical values +test("given an array with non-number values, it returns the sum of the numerical elements and ignores non-numerical values", () => { + const withNonNumbers = [ + { input: ["hey", 10, "hi", 60, 10], expected: 80 }, + { input: ["hello", "world", -10, -30, 10], expected: -30 }, + { input: [1, 20, "three", 4, "five", NaN], expected: 25 }, + { + input: [null, undefined, false, Infinity, 2.5, -12, -1.5], + expected: -11, + }, + { input: [1.5, "two", 3.5], expected: 5 }, + ]; -// 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 + withNonNumbers.forEach(({ input, expected }) => { + try { + expect(sum(input)).toEqual(expected); + } catch (error) { + throw new Error( + `Failed to return the correct sum ${expected} when given the array [${input}] containing non-number values: ${error.message}` + ); + } + }); +}); From ebbad7ea46c90190efbf855473a75fbd9b8e6a56 Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Wed, 25 Feb 2026 23:07:11 +0200 Subject: [PATCH 11/56] Fix: reduce number of decimal places returned for sum --- Sprint-1/implement/sum.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Sprint-1/implement/sum.js b/Sprint-1/implement/sum.js index 9062aafe3..590dff770 100644 --- a/Sprint-1/implement/sum.js +++ b/Sprint-1/implement/sum.js @@ -1,4 +1,11 @@ function sum(elements) { + const filteredElements = elements.filter((element) => + Number.isFinite(element) + ); + + return parseFloat( + filteredElements.reduce((total, num) => total + num, 0).toFixed(10) + ); } module.exports = sum; From 094010371d1df5049bebac8018c2d46921db42a7 Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Wed, 25 Feb 2026 23:11:58 +0200 Subject: [PATCH 12/56] Refactor to use for of loop --- Sprint-1/refactor/includes.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Sprint-1/refactor/includes.js b/Sprint-1/refactor/includes.js index 29dad81f0..a2ed75359 100644 --- a/Sprint-1/refactor/includes.js +++ b/Sprint-1/refactor/includes.js @@ -1,11 +1,6 @@ -// 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]; - if (element === target) { - return true; - } + for (const item of list) { + if (item === target) return true; } return false; } From 1c72bab5b21be015d9b8a0c9eea39b0820d0d009 Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Tue, 3 Mar 2026 12:58:40 +0200 Subject: [PATCH 13/56] Debug: address.js --- Sprint-2/debug/address.js | 11 +++++------ Sprint-2/package-lock.json | 2 ++ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Sprint-2/debug/address.js b/Sprint-2/debug/address.js index 940a6af83..7dd3f55c0 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,9 @@ const address = { postcode: "XYZ 123", }; +/* Prediction */ +// This will throw an error. This is because unlike lists, key values are selected using a '.' console.log(`My house number is ${address[0]}`); + +/* Fix */ +console.log(`My house number is ${address.houseNumber}`); diff --git a/Sprint-2/package-lock.json b/Sprint-2/package-lock.json index 9b4c725d6..ceda7296e 100644 --- a/Sprint-2/package-lock.json +++ b/Sprint-2/package-lock.json @@ -56,6 +56,7 @@ "integrity": "sha512-Oixnb+DzmRT30qu9d3tJSQkxuygWm32DFykT4bRoORPa9hZ/L4KhVB/XiRm6KG+roIEM7DBQlmg27kw2HZkdZg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.25.7", @@ -1368,6 +1369,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001663", "electron-to-chromium": "^1.5.28", From 88d95b1b4eea914acfa1d0e7ab4d5abfbd83ba55 Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Tue, 3 Mar 2026 18:03:00 +0200 Subject: [PATCH 14/56] Debug author --- Sprint-2/debug/author.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Sprint-2/debug/author.js b/Sprint-2/debug/author.js index 8c2125977..4c7cd4e5a 100644 --- a/Sprint-2/debug/author.js +++ b/Sprint-2/debug/author.js @@ -11,6 +11,14 @@ const author = { alive: true, }; -for (const value of author) { - console.log(value); -} +/* Prediction */ +// It will print out only the name of the keys (i.e. firstName, lastName, occupation, age, and alive), but not their actual values +// for (const { value, key } of author) { +// console.log(value); +// } + +// /* Fix */ +// for (const key of author) { +// console.log(author[key]); +// } + From c0f7bb1ae0e41d31ce070c1697993454c96b7a96 Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Sat, 7 Mar 2026 02:46:21 +0200 Subject: [PATCH 15/56] Fix: correct for loop --- Sprint-2/debug/author.js | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/Sprint-2/debug/author.js b/Sprint-2/debug/author.js index 4c7cd4e5a..6aff8eee2 100644 --- a/Sprint-2/debug/author.js +++ b/Sprint-2/debug/author.js @@ -1,8 +1,3 @@ -// 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", @@ -11,14 +6,26 @@ const author = { alive: true, }; -/* Prediction */ -// It will print out only the name of the keys (i.e. firstName, lastName, occupation, age, and alive), but not their actual values -// for (const { value, key } of author) { -// console.log(value); -// } +/* ======== Initial Script ======= */ +try { + for (const value of author) { + console.log(value); + } +} catch (error) { + console.error("Error:", error.message); +} + +/* ============== Prediction ============== */ +// It will print out only the name of the keys (i.e. firstName, lastName, +// occupation, age, and alive), but not their actual values. -// /* Fix */ -// for (const key of author) { -// console.log(author[key]); -// } +/* ============== Actual Result ============== */ +// Error: author is not iterable. +// This error occurs because the for...of loop can only be used with +// iterable objects like arrays, strings, etc. Since 'author' is an object, +// it is not iterable. To fix this, we can use a for...in loop instead. +/* ============== Corrected Script ============== */ +for (const key in author) { + console.log(author[key]); +} From cb55db5459ee0f695839e7f3c0b301ab7f1c02eb Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Sat, 7 Mar 2026 02:47:56 +0200 Subject: [PATCH 16/56] Fix: correct for loop --- Sprint-2/debug/author.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sprint-2/debug/author.js b/Sprint-2/debug/author.js index 6aff8eee2..030f4da04 100644 --- a/Sprint-2/debug/author.js +++ b/Sprint-2/debug/author.js @@ -1,3 +1,4 @@ +/* ======== Initial Script ======= */ const author = { firstName: "Zadie", lastName: "Smith", @@ -6,7 +7,6 @@ const author = { alive: true, }; -/* ======== Initial Script ======= */ try { for (const value of author) { console.log(value); From 81f9946c82c90fc625ae6c01d9ebfd2b25d5ab5c Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Sat, 7 Mar 2026 03:03:54 +0200 Subject: [PATCH 17/56] Fix: print property instead of entire object --- Sprint-2/debug/recipe.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Sprint-2/debug/recipe.js b/Sprint-2/debug/recipe.js index 6cbdd22cd..249c7e216 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -4,6 +4,7 @@ // Each ingredient should be logged on a new line // How can you fix it? +/* ============== Initial Script ============== */ const recipe = { title: "bruschetta", serves: 2, @@ -13,3 +14,18 @@ const recipe = { console.log(`${recipe.title} serves ${recipe.serves} ingredients: ${recipe}`); + +/* ============== Prediction ============== */ +// It will throw an error when trying to print the ingredients. +// This is because the entire recipe object was called instead of +// just its ingredients property. JavaScript cannot print an object, +// hence the error is thrown. + +/// ============== Actual Result ============== */ +// It tunrs out that JavaScript does not throw an error when +// trying to print an object. Instead, it prints out [object Object]. + +/* ============== Corrected Script ============== */ +console.log(`${recipe.title} serves ${recipe.serves} + ingredients: + - ${recipe.ingredients.join("\n - ")}`); From 1c5c12194a1a9bb6e3295a4efcd42e6f8ea161af Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Sun, 8 Mar 2026 16:41:59 +0200 Subject: [PATCH 18/56] Test: Case 1 -- prop exists in object --- Sprint-2/implement/contains.test.js | 64 ++++++++++++++--------------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index 326bdb1f2..c09233598 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -1,35 +1,33 @@ 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 - -// 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 +// Case 1: Should return true if the property exists in object. +test("should return true when object contains passed property name", () => { + const objsWithValidProps = [ + [{ a: 1, b: 2 }, "b"], + [{ name: "John", age: 30 }, "name"], + [{ nested: { key: "value" } }, "nested"], + [{ id: 123, status: "active", language: "JavaScript" }, "status"], + [{ data: [], items: null }, "data"], + ]; + + objsWithValidProps.forEach((obj, prop) => { + try { + expect(contains(obj, prop)).toEqual(true); + } catch (error) { + throw new Error( + `Failed to return true when ${prop} is present in obj: ${error.message}` + ); + } + }); +}); + +// Case 2: Should return false if the object does not contain the given property. +test.todo( + "should return false when object does not contain passed property name" +); + +// Case 3: Should return false if the object is empty. +test.todo("should return false when object is empty"); + +// Case 4: Should throw an error if a non-object is passed +test.todo("should throw error when non-object is passed"); From b22a5179a571ee049f5b6deef6bed05f4a72aeb1 Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Sun, 8 Mar 2026 16:53:19 +0200 Subject: [PATCH 19/56] Fix: correct passing obj and prop into test --- Sprint-2/implement/contains.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index c09233598..f97c723a0 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -10,7 +10,7 @@ test("should return true when object contains passed property name", () => { [{ data: [], items: null }, "data"], ]; - objsWithValidProps.forEach((obj, prop) => { + objsWithValidProps.forEach(([obj, prop]) => { try { expect(contains(obj, prop)).toEqual(true); } catch (error) { From 768954632a3cb374ac1e9ddc07e0e5d2810cee66 Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Sun, 8 Mar 2026 16:55:34 +0200 Subject: [PATCH 20/56] Test: Case 2 for when property not present in object --- Sprint-2/implement/contains.test.js | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index f97c723a0..8877963c4 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -22,9 +22,25 @@ test("should return true when object contains passed property name", () => { }); // Case 2: Should return false if the object does not contain the given property. -test.todo( - "should return false when object does not contain passed property name" -); +test("should return false when object does not contain passed property name", () => { + const objsWithoutProps = [ + [{ a: 1, b: 2 }, "c"], + [{ name: "John", age: 30 }, "email"], + [{ nested: { key: "value" } }, "nonexistent"], + [{ id: 123, status: "active", language: "JavaScript" }, "description"], + [{ data: [], items: null }, "nonexistent"], + ]; + + objsWithoutProps.forEach(([obj, prop]) => { + try { + expect(contains(obj, prop)).toEqual(false); + } catch (error) { + throw new Error( + `Failed to return false when ${prop} is not present in obj: ${error.message}` + ); + } + }); +}); // Case 3: Should return false if the object is empty. test.todo("should return false when object is empty"); From ceebce1b3848fffd93d417a71c4c10419c2d64e1 Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Sun, 8 Mar 2026 16:58:04 +0200 Subject: [PATCH 21/56] Test: Case 3 for empty object --- Sprint-2/implement/contains.test.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index 8877963c4..d2a620072 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -43,7 +43,15 @@ test("should return false when object does not contain passed property name", () }); // Case 3: Should return false if the object is empty. -test.todo("should return false when object is empty"); +test("should return false when object is empty", () => { + try { + expect(contains({}, "anyProperty")).toEqual(false); + } catch (error) { + throw new Error( + `Failed to return false when object is empty: ${error.message}` + ); + } +}); // Case 4: Should throw an error if a non-object is passed test.todo("should throw error when non-object is passed"); From b688460511031465f9032f4665dfb2d37796330d Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Sun, 8 Mar 2026 17:20:14 +0200 Subject: [PATCH 22/56] Test: Case 3 for empty object --- Sprint-2/implement/contains.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index d2a620072..54f6803e4 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -54,4 +54,4 @@ test("should return false when object is empty", () => { }); // Case 4: Should throw an error if a non-object is passed -test.todo("should throw error when non-object is passed"); +test("should throw error when non-object is passed"); From fdc221dc43b59c0ca3ac5f07abc42be42256bf0b Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Sun, 8 Mar 2026 17:48:42 +0200 Subject: [PATCH 23/56] Test: Case 4 for invalid objects --- Sprint-2/implement/contains.test.js | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index 54f6803e4..2f281ef06 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -36,7 +36,7 @@ test("should return false when object does not contain passed property name", () expect(contains(obj, prop)).toEqual(false); } catch (error) { throw new Error( - `Failed to return false when ${prop} is not present in obj: ${error.message}` + `Failed to return false when ${ob} is not present in obj: ${error.message}` ); } }); @@ -54,4 +54,24 @@ test("should return false when object is empty", () => { }); // Case 4: Should throw an error if a non-object is passed -test("should throw error when non-object is passed"); +test("should throw error when non-object is passed", () => { + const nonObjects = [ + null, + undefined, + 42, + "The Curse", + true, + Infinity, + ["string"], + ]; + + nonObjects.forEach((nonObj) => { + try { + expect(() => contains(nonObj, "prop")).toThrow(); + } catch (error) { + throw new Error( + `Failed to throw error when non-object ${nonObj} is passed: ${error.message}` + ); + } + }); +}); From c68b85ccac618a28ce8e69162bd3c776ca044a59 Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Sun, 8 Mar 2026 17:51:33 +0200 Subject: [PATCH 24/56] Implement contains.js --- Sprint-2/implement/contains.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Sprint-2/implement/contains.js b/Sprint-2/implement/contains.js index cd779308a..affcac824 100644 --- a/Sprint-2/implement/contains.js +++ b/Sprint-2/implement/contains.js @@ -1,3 +1,11 @@ -function contains() {} +function contains(object, property) { + if (object.constructor !== Object) { + throw new Error( + "First argument must be an object in the form { key: value }" + ); + } + + return property in object; +} module.exports = contains; From 49e36bbc38ffcfdb6b99ad83ab399b286ea42696 Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Sun, 8 Mar 2026 18:08:52 +0200 Subject: [PATCH 25/56] Refactor with describe --- Sprint-2/implement/contains.test.js | 124 ++++++++++++++-------------- 1 file changed, 63 insertions(+), 61 deletions(-) diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index 2f281ef06..f5dcf77ba 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -1,77 +1,79 @@ const contains = require("./contains.js"); -// Case 1: Should return true if the property exists in object. -test("should return true when object contains passed property name", () => { - const objsWithValidProps = [ - [{ a: 1, b: 2 }, "b"], - [{ name: "John", age: 30 }, "name"], - [{ nested: { key: "value" } }, "nested"], - [{ id: 123, status: "active", language: "JavaScript" }, "status"], - [{ data: [], items: null }, "data"], - ]; +describe("contains", () => { + // Case 1: Should return true if the property exists in object. + test("should return true when object contains passed property name", () => { + const objsWithValidProps = [ + [{ a: 1, b: 2 }, "b"], + [{ name: "John", age: 30 }, "name"], + [{ nested: { key: "value" } }, "nested"], + [{ id: 123, status: "active", language: "JavaScript" }, "status"], + [{ data: [], items: null }, "data"], + ]; - objsWithValidProps.forEach(([obj, prop]) => { - try { - expect(contains(obj, prop)).toEqual(true); - } catch (error) { - throw new Error( - `Failed to return true when ${prop} is present in obj: ${error.message}` - ); - } + objsWithValidProps.forEach(([obj, prop]) => { + try { + expect(contains(obj, prop)).toEqual(true); + } catch (error) { + throw new Error( + `Failed to return true when ${prop} is present in obj: ${error.message}` + ); + } + }); }); -}); -// Case 2: Should return false if the object does not contain the given property. -test("should return false when object does not contain passed property name", () => { - const objsWithoutProps = [ - [{ a: 1, b: 2 }, "c"], - [{ name: "John", age: 30 }, "email"], - [{ nested: { key: "value" } }, "nonexistent"], - [{ id: 123, status: "active", language: "JavaScript" }, "description"], - [{ data: [], items: null }, "nonexistent"], - ]; + // Case 2: Should return false if the object does not contain the given property. + test("should return false when object does not contain passed property name", () => { + const objsWithoutProps = [ + [{ a: 1, b: 2 }, "c"], + [{ name: "John", age: 30 }, "email"], + [{ nested: { key: "value" } }, "nonexistent"], + [{ id: 123, status: "active", language: "JavaScript" }, "description"], + [{ data: [], items: null }, "nonexistent"], + ]; + + objsWithoutProps.forEach(([obj, prop]) => { + try { + expect(contains(obj, prop)).toEqual(false); + } catch (error) { + throw new Error( + `Failed to return false when ${prop} is not present in obj: ${error.message}` + ); + } + }); + }); - objsWithoutProps.forEach(([obj, prop]) => { + // Case 3: Should return false if the object is empty. + test("should return false when object is empty", () => { try { - expect(contains(obj, prop)).toEqual(false); + expect(contains({}, "anyProperty")).toEqual(false); } catch (error) { throw new Error( - `Failed to return false when ${ob} is not present in obj: ${error.message}` + `Failed to return false when object is empty: ${error.message}` ); } }); -}); - -// Case 3: Should return false if the object is empty. -test("should return false when object is empty", () => { - try { - expect(contains({}, "anyProperty")).toEqual(false); - } catch (error) { - throw new Error( - `Failed to return false when object is empty: ${error.message}` - ); - } -}); -// Case 4: Should throw an error if a non-object is passed -test("should throw error when non-object is passed", () => { - const nonObjects = [ - null, - undefined, - 42, - "The Curse", - true, - Infinity, - ["string"], - ]; + // Case 4: Should throw an error if a non-object is passed + test("should throw error when non-object is passed", () => { + const nonObjects = [ + null, + undefined, + 42, + "The Curse", + true, + Infinity, + ["string"], + ]; - nonObjects.forEach((nonObj) => { - try { - expect(() => contains(nonObj, "prop")).toThrow(); - } catch (error) { - throw new Error( - `Failed to throw error when non-object ${nonObj} is passed: ${error.message}` - ); - } + nonObjects.forEach((nonObj) => { + try { + expect(() => contains(nonObj, "prop")).toThrow(); + } catch (error) { + throw new Error( + `Failed to throw error when non-object ${nonObj} is passed: ${error.message}` + ); + } + }); }); }); From 7ad5748a060ced4a347f702b6bc7dade7fa50d08 Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Wed, 18 Mar 2026 01:01:23 +0200 Subject: [PATCH 26/56] Test: add test for Case 2 --- Sprint-2/implement/lookup.test.js | 39 +++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/Sprint-2/implement/lookup.test.js b/Sprint-2/implement/lookup.test.js index 547e06c5a..f50f82bb6 100644 --- a/Sprint-2/implement/lookup.test.js +++ b/Sprint-2/implement/lookup.test.js @@ -1,9 +1,44 @@ const createLookup = require("./lookup.js"); -test.todo("creates a country currency code lookup for multiple codes"); +// Case 1: Returns country currency code lookup for a single country-currency pair +test("creates a country currency code lookup for a single code pair", () => { + const countryCurrencyPairs = [["US", "USD"]]; -/* + const currencyObj = createLookup(countryCurrencyPairs); + + expect(currencyObj).toEqual({ + US: "USD", + }); +}); + +// Case 2: Returns country currency codes lookup for multiple country-currency pairs +test("creates a country currency code lookup for multiple codes", () => { + try { + const countryCurrencyPairs = [ + ["US", "USD"], + ["CA", "CAD"], + ["GB", "GBP"], + ["ZA", "ZAR"], + ["NG", "NGN"], + ]; + const currencyObj = createLookup(countryCurrencyPairs); + + expect(currencyObj).toEqual({ + US: "USD", + CA: "CAD", + GB: "GBP", + ZA: "ZAR", + NG: "NGN", + }); + } catch (error) { + throw new Error( + `Failed to create the currency lookup object for ${JSON.stringify(countryCurrencyPairs)}: ${error.message}` + ); + } +}); + +/* Create a lookup object of key value pairs from an array of code pairs Acceptance Criteria: From 34d7c95b7dfadb84c867d3402fcc9774fed9f86c Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Wed, 18 Mar 2026 01:05:28 +0200 Subject: [PATCH 27/56] Refactor: remove unnecessary try/catch --- Sprint-2/implement/lookup.test.js | 36 +++++++++++++------------------ 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/Sprint-2/implement/lookup.test.js b/Sprint-2/implement/lookup.test.js index f50f82bb6..c8f6d6a27 100644 --- a/Sprint-2/implement/lookup.test.js +++ b/Sprint-2/implement/lookup.test.js @@ -13,29 +13,23 @@ test("creates a country currency code lookup for a single code pair", () => { // Case 2: Returns country currency codes lookup for multiple country-currency pairs test("creates a country currency code lookup for multiple codes", () => { - try { - const countryCurrencyPairs = [ - ["US", "USD"], - ["CA", "CAD"], - ["GB", "GBP"], - ["ZA", "ZAR"], - ["NG", "NGN"], - ]; + const countryCurrencyPairs = [ + ["US", "USD"], + ["CA", "CAD"], + ["GB", "GBP"], + ["ZA", "ZAR"], + ["NG", "NGN"], + ]; - const currencyObj = createLookup(countryCurrencyPairs); + const currencyObj = createLookup(countryCurrencyPairs); - expect(currencyObj).toEqual({ - US: "USD", - CA: "CAD", - GB: "GBP", - ZA: "ZAR", - NG: "NGN", - }); - } catch (error) { - throw new Error( - `Failed to create the currency lookup object for ${JSON.stringify(countryCurrencyPairs)}: ${error.message}` - ); - } + expect(currencyObj).toEqual({ + US: "USD", + CA: "CAD", + GB: "GBP", + ZA: "ZAR", + NG: "NGN", + }); }); /* From 47113d83ecf1b9f2a759b86fa45f34c5455d5dfd Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Fri, 27 Mar 2026 23:35:55 +0200 Subject: [PATCH 28/56] Refactor for readability --- Sprint-2/implement/lookup.test.js | 146 ++++++++++++++++++++---------- 1 file changed, 97 insertions(+), 49 deletions(-) diff --git a/Sprint-2/implement/lookup.test.js b/Sprint-2/implement/lookup.test.js index c8f6d6a27..bcc85f149 100644 --- a/Sprint-2/implement/lookup.test.js +++ b/Sprint-2/implement/lookup.test.js @@ -1,64 +1,112 @@ const createLookup = require("./lookup.js"); -// Case 1: Returns country currency code lookup for a single country-currency pair -test("creates a country currency code lookup for a single code pair", () => { - const countryCurrencyPairs = [["US", "USD"]]; +describe("createLookup", () => { + // Case 1: Returns an empty object if no country-currency pairs are provided + test("returns an empty object if no country-currency pairs are provided", () => { + const countryCurrencyPairs = []; + const currencyObj = createLookup(countryCurrencyPairs); - const currencyObj = createLookup(countryCurrencyPairs); + expect(currencyObj).toEqual({}); + }); + + // Case 2: Returns country currency code lookup for a single country-currency pair + test("creates a country currency code lookup for a single code pair", () => { + const countryCurrencyPairs = [["US", "USD"]]; + const currencyObj = createLookup(countryCurrencyPairs); - expect(currencyObj).toEqual({ - US: "USD", + expect(currencyObj).toEqual({ + US: "USD", + }); }); -}); -// Case 2: Returns country currency codes lookup for multiple country-currency pairs -test("creates a country currency code lookup for multiple codes", () => { - const countryCurrencyPairs = [ - ["US", "USD"], - ["CA", "CAD"], - ["GB", "GBP"], - ["ZA", "ZAR"], - ["NG", "NGN"], - ]; - - const currencyObj = createLookup(countryCurrencyPairs); - - expect(currencyObj).toEqual({ - US: "USD", - CA: "CAD", - GB: "GBP", - ZA: "ZAR", - NG: "NGN", + // Case 3: Returns country currency codes lookup for multiple country-currency pairs + test("creates a country currency code lookup for multiple codes", () => { + const countryCurrencyPairs = [ + ["US", "USD"], + ["CA", "CAD"], + ["GB", "GBP"], + ["ZA", "ZAR"], + ["NG", "NGN"], + ]; + + const inputCurrencyPairObj = createLookup(countryCurrencyPairs); + const outputCurrencyPairObj = { + US: "USD", + CA: "CAD", + GB: "GBP", + ZA: "ZAR", + NG: "NGN", + }; + + expect(inputCurrencyPairObj).toEqual(outputCurrencyPairObj); }); -}); -/* -Create a lookup object of key value pairs from an array of code pairs + // Case 4: Throws an error if a country-currency pair is not an array + test("throws an error if a country-currency pair is not an array", () => { + const countryCurrencyPairs = [ + ["US", "USD"], + ["CA", "CAD"], + "GB-GBP", + ["ZA", "ZAR"], + ["NG", "NGN"], + ]; + + expect(() => createLookup(countryCurrencyPairs)).toThrow( + "Country-currency pairs must be in array format" + ); + }); -Acceptance Criteria: + // Case 5: Throws an error if a country-currency pair is missing a country or currency + test("throws an error if a country-currency pair is missing a country or currency", () => { + const countryCurrencyPairs = [ + ["US", ""], + ["", "CAD"], + [" ", "GBP"], + ]; -Given - - An array of arrays representing country code and currency code pairs - e.g. [['US', 'USD'], ['CA', 'CAD']] + for (const currencyPair of countryCurrencyPairs) { + expect(() => createLookup([currencyPair])).toThrow( + "Country and currency codes cannot be empty" + ); + } + }); -When - - createLookup function is called with the country-currency array as an argument + // Case 6: Throws an error if a country-currency pair contains more than two elements + test("throws an error if a country-currency pair contains more than two elements", () => { + const countryCurrencyPairs = [["GB", "GBP", "ZAR"]]; -Then - - It should return an object where: - - The keys are the country codes - - The values are the corresponding currency codes + expect(() => createLookup(countryCurrencyPairs)).toThrow( + "Country-currency pairs must contain exactly two elements: country and currency" + ); + }); -Example -Given: [['US', 'USD'], ['CA', 'CAD']] + // Case 7: Throws and error if a country-currency pair is duplicated + test("throws an error if a country-currency pair is duplicated", () => { + const countryCurrencyPairs = [ + ["US", "USD"], + ["CA", "CAD"], + ["US", "USD"], + ]; -When -createLookup(countryCurrencyPairs) is called + expect(() => createLookup(countryCurrencyPairs)).toThrow( + "Duplicate country code found: US" + ); + }); -Then -It should return: - { - 'US': 'USD', - 'CA': 'CAD' - } -*/ + // Case 8: Throws an error if non-string values are used as country or currency codes + test("throws an error if non-string values are used as country or currency codes", () => { + const countryCurrencyPairs = [ + [{ name: "United States" }, "USD"], + ["CA", Infinity], + ["GB", 1.21], + [undefined, "ZAR"], + ["NG", null], + ]; + + for (const currencyPair of countryCurrencyPairs) { + expect(() => createLookup([currencyPair])).toThrow( + "Country-currency pairs must be in string format" + ); + } + }); +}); From 8aefebd2e511fb7af686fa415fdcd5c4321f090f Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Fri, 27 Mar 2026 23:39:10 +0200 Subject: [PATCH 29/56] Fix: reorder validation checks to prevent weird results --- Sprint-2/implement/lookup.js | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/Sprint-2/implement/lookup.js b/Sprint-2/implement/lookup.js index a6746e07f..8b46fd14a 100644 --- a/Sprint-2/implement/lookup.js +++ b/Sprint-2/implement/lookup.js @@ -1,5 +1,35 @@ -function createLookup() { - // implementation here +function createLookup(countryCurrencyPairs) { + let countryCurrencyObj = {}; + + for (const countryCurrencyPair of countryCurrencyPairs) { + if (!Array.isArray(countryCurrencyPair)) { + throw new Error("Country-currency pairs must be in array format"); + } + + if (countryCurrencyPair.length !== 2) { + throw new Error( + "Country-currency pairs must contain exactly two elements: country and currency" + ); + } + + let [country, currency] = countryCurrencyPair; + + if (typeof country !== "string" || typeof currency !== "string") { + throw new Error("Country-currency pairs must be in string format"); + } + + if (country.trim() === "" || currency.trim() === "") { + throw new Error("Country and currency codes cannot be empty"); + } + + if (countryCurrencyObj[country]) { + throw new Error(`Duplicate country code found: ${country}`); + } + + countryCurrencyObj[country] = currency; + } + + return countryCurrencyObj; } module.exports = createLookup; From ff9eac060f5eff9c2ae4a5db299298131d29ae13 Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Fri, 27 Mar 2026 23:54:18 +0200 Subject: [PATCH 30/56] Refactor: remove unnecessary try/catch wrapping --- Sprint-2/implement/contains.test.js | 32 ++++------------------------- 1 file changed, 4 insertions(+), 28 deletions(-) diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index f5dcf77ba..e3753ebaf 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -12,13 +12,7 @@ describe("contains", () => { ]; objsWithValidProps.forEach(([obj, prop]) => { - try { - expect(contains(obj, prop)).toEqual(true); - } catch (error) { - throw new Error( - `Failed to return true when ${prop} is present in obj: ${error.message}` - ); - } + expect(contains(obj, prop)).toEqual(true); }); }); @@ -33,25 +27,13 @@ describe("contains", () => { ]; objsWithoutProps.forEach(([obj, prop]) => { - try { - expect(contains(obj, prop)).toEqual(false); - } catch (error) { - throw new Error( - `Failed to return false when ${prop} is not present in obj: ${error.message}` - ); - } + expect(contains(obj, prop)).toEqual(false); }); }); // Case 3: Should return false if the object is empty. test("should return false when object is empty", () => { - try { - expect(contains({}, "anyProperty")).toEqual(false); - } catch (error) { - throw new Error( - `Failed to return false when object is empty: ${error.message}` - ); - } + expect(contains({}, "anyProperty")).toEqual(false); }); // Case 4: Should throw an error if a non-object is passed @@ -67,13 +49,7 @@ describe("contains", () => { ]; nonObjects.forEach((nonObj) => { - try { - expect(() => contains(nonObj, "prop")).toThrow(); - } catch (error) { - throw new Error( - `Failed to throw error when non-object ${nonObj} is passed: ${error.message}` - ); - } + expect(() => contains(nonObj, "prop")).toThrow(); }); }); }); From 2743b71330dc01c5b36ce7651174d9b780448b59 Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Sat, 28 Mar 2026 06:12:01 +0200 Subject: [PATCH 31/56] Ensure prototype properties aren't incorrectly returned as true --- Sprint-2/implement/contains.test.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index e3753ebaf..2eed6c1d7 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -36,7 +36,20 @@ describe("contains", () => { expect(contains({}, "anyProperty")).toEqual(false); }); - // Case 4: Should throw an error if a non-object is passed + // Case 4: Should return false for properties that only exist in the prototype chain + test("should return false for properties in prototype chain", () => { + const objsWithProtoProps = [ + [{ a: 1 }, "toString"], + [{ name: "John", age: 30 }, "hasOwnProperty"], + [{ nested: { key: "value" } }, "isPrototypeOf"], + ]; + + objsWithProtoProps.forEach(([obj, prop]) => { + expect(contains(obj, prop)).toEqual(false); + }); + }); + + // Case 5: Should throw an error if a non-object is passed test("should throw error when non-object is passed", () => { const nonObjects = [ null, From 672d4590bdca6dd6bff2eb9e8abfe0a5e5941ff5 Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Sat, 28 Mar 2026 06:13:11 +0200 Subject: [PATCH 32/56] Fix: ensure prototype properties don't return true --- Sprint-2/implement/contains.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sprint-2/implement/contains.js b/Sprint-2/implement/contains.js index affcac824..0fe0182a2 100644 --- a/Sprint-2/implement/contains.js +++ b/Sprint-2/implement/contains.js @@ -5,7 +5,7 @@ function contains(object, property) { ); } - return property in object; + return object.hasOwnProperty(property); } module.exports = contains; From e6cde3f9f9ba47815c74bc69c7f72cce8f3c40a0 Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Sat, 28 Mar 2026 06:36:52 +0200 Subject: [PATCH 33/56] Check for nulls and arrays --- Sprint-2/implement/contains.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sprint-2/implement/contains.js b/Sprint-2/implement/contains.js index 0fe0182a2..81ceb814b 100644 --- a/Sprint-2/implement/contains.js +++ b/Sprint-2/implement/contains.js @@ -1,5 +1,5 @@ function contains(object, property) { - if (object.constructor !== Object) { + if (typeof object !== "object" || object === null || Array.isArray(object)) { throw new Error( "First argument must be an object in the form { key: value }" ); From c837ff12388c77861007fe7aa6c38e7d9ad7a06d Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Sat, 28 Mar 2026 10:35:32 +0200 Subject: [PATCH 34/56] Refactor --- Sprint-2/implement/contains.js | 2 +- Sprint-2/implement/contains.test.js | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Sprint-2/implement/contains.js b/Sprint-2/implement/contains.js index 81ceb814b..c2cc039e3 100644 --- a/Sprint-2/implement/contains.js +++ b/Sprint-2/implement/contains.js @@ -1,6 +1,6 @@ function contains(object, property) { if (typeof object !== "object" || object === null || Array.isArray(object)) { - throw new Error( + throw new TypeError( "First argument must be an object in the form { key: value }" ); } diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index 2eed6c1d7..93df9b50a 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -62,7 +62,9 @@ describe("contains", () => { ]; nonObjects.forEach((nonObj) => { - expect(() => contains(nonObj, "prop")).toThrow(); + expect(() => contains(nonObj, "prop")).toThrow( + "First argument must be an object in the form { key: value }" + ); }); }); }); From 273dc37e1a00b27f2dbbe70ba1ea5037645be947 Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Sat, 28 Mar 2026 19:08:45 +0200 Subject: [PATCH 35/56] Test: empty object input --- Sprint-2/implement/tally.test.js | 38 +++++--------------------------- 1 file changed, 6 insertions(+), 32 deletions(-) diff --git a/Sprint-2/implement/tally.test.js b/Sprint-2/implement/tally.test.js index 2ceffa8dd..afa311e8a 100644 --- a/Sprint-2/implement/tally.test.js +++ b/Sprint-2/implement/tally.test.js @@ -1,34 +1,8 @@ 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 +describe("tally", () => { + // Case 1: Returns an empty object when an empty array is passed to tally + test("tally on an empty array returns an empty object", () => { + expect(tally([])).toEqual({}); + }); +}); From eb1dee9a79991f4a3fe5eddfd4342a51b5a7e52d Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Sat, 28 Mar 2026 19:34:49 +0200 Subject: [PATCH 36/56] Test: array without duplicates --- Sprint-2/implement/tally.test.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Sprint-2/implement/tally.test.js b/Sprint-2/implement/tally.test.js index afa311e8a..88563674e 100644 --- a/Sprint-2/implement/tally.test.js +++ b/Sprint-2/implement/tally.test.js @@ -1,8 +1,23 @@ const tally = require("./tally.js"); -describe("tally", () => { +describe("tally()", () => { // Case 1: Returns an empty object when an empty array is passed to tally test("tally on an empty array returns an empty object", () => { expect(tally([])).toEqual({}); }); + + // Case 2: Returns counts for each unique item when given an array without duplicate items + test("tally on an array without duplicate items returns counts for each unique item", () => { + const tallyWithUniqueItems = [ + [["a"], { a: 1 }], + [["a", "b", "c"], { a: 1, b: 1, c: 1 }], + [[1, 2, 3], { 1: 1, 2: 1, 3: 1 }], + [[true, false], { true: 1, false: 1 }], + [[null, undefined], { null: 1, undefined: 1 }], + ]; + + tallyWithUniqueItems.forEach(([inputArray, targetOutput]) => { + expect(tally(inputArray)).toEqual(targetOutput); + }); + }); }); From 340015c9e46dae6e101431b3cf4a70aaec7da816 Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Sat, 28 Mar 2026 19:37:27 +0200 Subject: [PATCH 37/56] Implementation of tally function --- Sprint-2/implement/tally.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index f47321812..cafa61dfa 100644 --- a/Sprint-2/implement/tally.js +++ b/Sprint-2/implement/tally.js @@ -1,3 +1,19 @@ -function tally() {} +function tally(array) { + if (!Array.isArray(array)) { + throw new Error("Input must be an array"); + } + + const counts = {}; + + for (const prop of array) { + if (Object.hasOwn(tally, prop)) { + counts[prop] += 1; + } else { + counts[prop] = 1; + } + } + + return counts; +} module.exports = tally; From 420bc1f2961015a168803b5a64c5d7d75b1b0b0a Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Sat, 28 Mar 2026 19:41:24 +0200 Subject: [PATCH 38/56] Fix: correct tally variable with counts --- Sprint-2/implement/tally.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index cafa61dfa..51cd4194b 100644 --- a/Sprint-2/implement/tally.js +++ b/Sprint-2/implement/tally.js @@ -6,7 +6,7 @@ function tally(array) { const counts = {}; for (const prop of array) { - if (Object.hasOwn(tally, prop)) { + if (Object.hasOwn(counts, prop)) { counts[prop] += 1; } else { counts[prop] = 1; @@ -16,4 +16,6 @@ function tally(array) { return counts; } +console.log(tally(["a", "a", "b", "c"])); + module.exports = tally; From 8919eac9dfe2be88f5508da07a939cd35e01f6f8 Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Sat, 28 Mar 2026 19:41:58 +0200 Subject: [PATCH 39/56] Test: array with duplicates --- Sprint-2/implement/tally.test.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Sprint-2/implement/tally.test.js b/Sprint-2/implement/tally.test.js index 88563674e..b3de7934c 100644 --- a/Sprint-2/implement/tally.test.js +++ b/Sprint-2/implement/tally.test.js @@ -20,4 +20,18 @@ describe("tally()", () => { expect(tally(inputArray)).toEqual(targetOutput); }); }); + + // Case 3: Return counts for each unique item when given an array with duplicate items + test("tally on an array with duplicate items returns counts for each unique item", () => { + const tallyWithDuplicateItems = [ + [["a", "a", "b", "c"], { a: 2, b: 1, c: 1 }], + [[1, 2, 2, 3], { 1: 1, 2: 2, 3: 1 }], + [[true, true, false], { true: 2, false: 1 }], + [[null, null, undefined], { null: 2, undefined: 1 }], + ]; + + tallyWithDuplicateItems.forEach(([inputArray, targetOutput]) => { + expect(tally(inputArray)).toEqual(targetOutput); + }); + }); }); From 8d56810ebb79a27624d98ca18b14b2b8703aaa27 Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Sat, 28 Mar 2026 19:43:46 +0200 Subject: [PATCH 40/56] Refactor: remove unnecessary debugging code --- Sprint-2/implement/tally.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index 51cd4194b..b825d41be 100644 --- a/Sprint-2/implement/tally.js +++ b/Sprint-2/implement/tally.js @@ -16,6 +16,4 @@ function tally(array) { return counts; } -console.log(tally(["a", "a", "b", "c"])); - module.exports = tally; From bf09c407a6b0445b3fa02071296e5686d388853b Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Sat, 28 Mar 2026 19:44:43 +0200 Subject: [PATCH 41/56] Test: invalid non array inputs --- Sprint-2/implement/tally.test.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Sprint-2/implement/tally.test.js b/Sprint-2/implement/tally.test.js index b3de7934c..972786bd3 100644 --- a/Sprint-2/implement/tally.test.js +++ b/Sprint-2/implement/tally.test.js @@ -34,4 +34,13 @@ describe("tally()", () => { expect(tally(inputArray)).toEqual(targetOutput); }); }); + + // Case 4: Throws an error when given an invalid input like a string + test("tally throws an error when passed a non-array input", () => { + const invalidInputs = ["invalid", 123, {}, null, undefined]; + + invalidInputs.forEach((input) => { + expect(() => tally(input)).toThrow("Input must be an array"); + }); + }); }); From 7e6ad117f5ac4471112d2dc01a20236ba5670cf5 Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Sat, 28 Mar 2026 19:58:55 +0200 Subject: [PATCH 42/56] Fix: split key-value pair on only first occurence of '=' --- Sprint-2/implement/querystring.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 45ec4e5f3..72d3ae65c 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -6,8 +6,9 @@ function parseQueryString(queryString) { const keyValuePairs = queryString.split("&"); for (const pair of keyValuePairs) { - const [key, value] = pair.split("="); - queryParams[key] = value; + // Split the pair on the first "=" to separate the key and value + const [key, ...value] = pair.split("="); + queryParams[key] = value.join("="); } return queryParams; From 21df2c264fdd66b94b33d9eb7bb2bba1c753990e Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Sat, 28 Mar 2026 20:06:14 +0200 Subject: [PATCH 43/56] Refactor: test for additional cases --- Sprint-2/implement/querystring.test.js | 32 +++++++++++++++++++------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/Sprint-2/implement/querystring.test.js b/Sprint-2/implement/querystring.test.js index 3e218b789..aaf67487a 100644 --- a/Sprint-2/implement/querystring.test.js +++ b/Sprint-2/implement/querystring.test.js @@ -1,12 +1,28 @@ -// 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. +const parseQueryString = require("./querystring.js"); -const parseQueryString = require("./querystring.js") +describe("parseQueryString", () => { + // Case 1: Returns an empty object when given an empty query string + test("parses an empty querystring", () => { + expect(parseQueryString("")).toEqual({}); + }); + + // Case 2: String contains one key-value pair and 1 '=' character + test("parses querystring values containing =", () => { + const singlePair = [ + ["name=John", { name: "John" }], + ["age=30", { age: "30" }], + ["city=New York", { city: "New York" }], + ]; + + singlePair.forEach(([input, targetOutput]) => { + expect(parseQueryString(input)).toEqual(targetOutput); + }); + }); -test("parses querystring values containing =", () => { - expect(parseQueryString("equation=x=y+1")).toEqual({ - "equation": "x=y+1", + // Case 3: String contains one key-value pair and multiple '=' character + test("parses querystring values containing multiple =", () => { + expect(parseQueryString("equation=x=y+1")).toEqual({ + equation: "x=y+1", + }); }); }); From 50e80fda0e74eb3328ffa584a461214128414066 Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Sat, 28 Mar 2026 20:30:29 +0200 Subject: [PATCH 44/56] Test: strings with multiple key-value pairs --- Sprint-2/implement/querystring.test.js | 57 ++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/Sprint-2/implement/querystring.test.js b/Sprint-2/implement/querystring.test.js index aaf67487a..eccd956f1 100644 --- a/Sprint-2/implement/querystring.test.js +++ b/Sprint-2/implement/querystring.test.js @@ -25,4 +25,61 @@ describe("parseQueryString", () => { equation: "x=y+1", }); }); + + // Case 4: String contains multiple key-value pairs separated by '&' with one '=' character each + test("parses multiple key-value pairs", () => { + const multiplePairs = [ + ["name=John&age=30", { name: "John", age: "30" }], + ["city=New York&country=USA", { city: "New York", country: "USA" }], + [ + "key1=value1&key2=value2&key3=value3", + { key1: "value1", key2: "value2", key3: "value3" }, + ], + [ + "Nicaragua=Managua&Honduras=Tegucigalpa&El Salvador=San Salvador&Costa Rica=San José", + { + Nicaragua: "Managua", + Honduras: "Tegucigalpa", + "El Salvador": "San Salvador", + "Costa Rica": "San José", + }, + ], + ]; + + multiplePairs.forEach(([input, targetOutput]) => { + expect(parseQueryString(input)).toEqual(targetOutput); + }); + }); + + // Case 5: String contains multiple key-value pairs separated by '&' with multiple '=' character each + test("parses multiple key-value pairs with multiple =", () => { + const multiplePairsWithEquals = [ + [ + "equation1=x=y+1&equation2=a=b+c", + { + equation1: "x=y+1", + }, + ], + [ + "equation2=a!=b+c&equation3=p=q*r", + { + equation2: "a!=b+c", + equation3: "p=q*r", + }, + ], + [ + "equation3=p!=q*r&equation4=m=n+o&equation5=x=y/1", + { + equation3: "p!=q*r", + equation4: "m=n+o", + equation5: "x=y/1", + }, + ], + ]; + + expect(parseQueryString("equation1=x=y+1&equation2=a=b+c")).toEqual({ + equation1: "x=y+1", + equation2: "a=b+c", + }); + }); }); From a9a06c301cd165c68a5f894ede28b3b9024823cd Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Sat, 28 Mar 2026 20:38:00 +0200 Subject: [PATCH 45/56] Validate input is string --- Sprint-2/implement/querystring.js | 5 +++++ Sprint-2/implement/querystring.test.js | 14 ++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 72d3ae65c..19d39032f 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -1,5 +1,10 @@ function parseQueryString(queryString) { const queryParams = {}; + + if (typeof queryString !== "string") { + throw new Error("Input must be a string"); + } + if (queryString.length === 0) { return queryParams; } diff --git a/Sprint-2/implement/querystring.test.js b/Sprint-2/implement/querystring.test.js index eccd956f1..3059dfa43 100644 --- a/Sprint-2/implement/querystring.test.js +++ b/Sprint-2/implement/querystring.test.js @@ -82,4 +82,18 @@ describe("parseQueryString", () => { equation2: "a=b+c", }); }); + + // Case 6: Throws an error when given an invalid input like a non-string + test("throws an error when passed a non-string input", () => { + const invalidInputs = [ + [123, -67, 3.14], + [{ name: "John Locke" }, { title: "Lost" }, {}], + [null], + [undefined], + ]; + + invalidInputs.forEach((input) => { + expect(() => parseQueryString(input)).toThrow("Input must be a string"); + }); + }); }); From 774cce1974bc980977171b39612ce39fcc60f6f9 Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Sat, 28 Mar 2026 21:25:41 +0200 Subject: [PATCH 46/56] Ensure that '=' with spaces are processed correctly --- Sprint-2/implement/querystring.js | 2 +- Sprint-2/implement/querystring.test.js | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 19d39032f..d3f4418cb 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -13,7 +13,7 @@ function parseQueryString(queryString) { for (const pair of keyValuePairs) { // Split the pair on the first "=" to separate the key and value const [key, ...value] = pair.split("="); - queryParams[key] = value.join("="); + queryParams[key.trim()] = value.join("=").trim(); } return queryParams; diff --git a/Sprint-2/implement/querystring.test.js b/Sprint-2/implement/querystring.test.js index 3059dfa43..a76ebc3c0 100644 --- a/Sprint-2/implement/querystring.test.js +++ b/Sprint-2/implement/querystring.test.js @@ -90,10 +90,31 @@ describe("parseQueryString", () => { [{ name: "John Locke" }, { title: "Lost" }, {}], [null], [undefined], + [ + [ + "equation1=x=y+1&equation2=a=b+c", + { + equation1: "x=y+1", + }, + ], + ], ]; invalidInputs.forEach((input) => { expect(() => parseQueryString(input)).toThrow("Input must be a string"); }); }); + + // Case 7: String contains a space between the key and value + test("parses querystring with spaces between key and value", () => { + const pairsWithSpaces = [ + ["name =John Doe", { name: "John Doe" }], + ["city = New York", { city: "New York" }], + ["country =United States", { country: "United States" }], + ]; + + pairsWithSpaces.forEach(([input, targetOutput]) => { + expect(parseQueryString(input)).toEqual(targetOutput); + }); + }); }); From 9873f8525265be727f047b09f8a4e4bb0b734b34 Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Sat, 28 Mar 2026 21:42:05 +0200 Subject: [PATCH 47/56] Fix: iterate over tests correctly --- Sprint-2/implement/querystring.test.js | 28 ++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/Sprint-2/implement/querystring.test.js b/Sprint-2/implement/querystring.test.js index a76ebc3c0..9588f0b49 100644 --- a/Sprint-2/implement/querystring.test.js +++ b/Sprint-2/implement/querystring.test.js @@ -58,6 +58,7 @@ describe("parseQueryString", () => { "equation1=x=y+1&equation2=a=b+c", { equation1: "x=y+1", + equation2: "a=b+c", }, ], [ @@ -77,9 +78,8 @@ describe("parseQueryString", () => { ], ]; - expect(parseQueryString("equation1=x=y+1&equation2=a=b+c")).toEqual({ - equation1: "x=y+1", - equation2: "a=b+c", + multiplePairsWithEquals.forEach(([input, targetOutput]) => { + expect(parseQueryString(input)).toEqual(targetOutput); }); }); @@ -87,9 +87,11 @@ describe("parseQueryString", () => { test("throws an error when passed a non-string input", () => { const invalidInputs = [ [123, -67, 3.14], - [{ name: "John Locke" }, { title: "Lost" }, {}], - [null], - [undefined], + { name: "John Locke" }, + { title: "Lost" }, + {}, + null, + undefined, [ [ "equation1=x=y+1&equation2=a=b+c", @@ -117,4 +119,18 @@ describe("parseQueryString", () => { expect(parseQueryString(input)).toEqual(targetOutput); }); }); + + // Case 8: Throws an error when given a string with missing key or value + test("throws an error when passed a string with missing key or value", () => { + const invalidQueryStrings = [ + ["=valueOnly"], + ["keyOnly="], + ["=valueOnly&key2=value2"], + ["key1=value1&=valueOnly"], + ]; + + invalidQueryStrings.forEach((input) => { + expect(() => parseQueryString(input)).toThrow(); + }); + }); }); From cd4b7b5727b53c87c7be0862e90e9f8ce4e38eff Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Sat, 28 Mar 2026 21:58:01 +0200 Subject: [PATCH 48/56] Fix: ensure input is stripped of spaces --- Sprint-2/implement/querystring.js | 6 +++--- Sprint-2/implement/querystring.test.js | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index d3f4418cb..25e203fdb 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -1,11 +1,11 @@ function parseQueryString(queryString) { - const queryParams = {}; - if (typeof queryString !== "string") { throw new Error("Input must be a string"); } - if (queryString.length === 0) { + const queryParams = {}; + + if (queryString.trim().length === 0) { return queryParams; } const keyValuePairs = queryString.split("&"); diff --git a/Sprint-2/implement/querystring.test.js b/Sprint-2/implement/querystring.test.js index 9588f0b49..75043b44d 100644 --- a/Sprint-2/implement/querystring.test.js +++ b/Sprint-2/implement/querystring.test.js @@ -4,6 +4,7 @@ describe("parseQueryString", () => { // Case 1: Returns an empty object when given an empty query string test("parses an empty querystring", () => { expect(parseQueryString("")).toEqual({}); + expect(parseQueryString(" ")).toEqual({}); }); // Case 2: String contains one key-value pair and 1 '=' character From 0c144f3a9ba8b34f5db57792bc16f2ef6a6024bb Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Sat, 28 Mar 2026 22:42:22 +0200 Subject: [PATCH 49/56] Ensure both key and value are provided --- Sprint-2/implement/querystring.js | 14 +++++++++++--- Sprint-2/implement/querystring.test.js | 12 +++++++----- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 25e203fdb..7950efac0 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -11,9 +11,17 @@ function parseQueryString(queryString) { const keyValuePairs = queryString.split("&"); for (const pair of keyValuePairs) { - // Split the pair on the first "=" to separate the key and value - const [key, ...value] = pair.split("="); - queryParams[key.trim()] = value.join("=").trim(); + const separatorIndex = pair.indexOf("="); + + if (separatorIndex <= 0 || separatorIndex === pair.length - 1) { + throw new Error( + "String must contain both a key and a value separated by '='" + ); + } + + const key = pair.slice(0, separatorIndex).trim(); + const value = pair.slice(separatorIndex + 1).trim(); + queryParams[key] = value; } return queryParams; diff --git a/Sprint-2/implement/querystring.test.js b/Sprint-2/implement/querystring.test.js index 75043b44d..0c17ce340 100644 --- a/Sprint-2/implement/querystring.test.js +++ b/Sprint-2/implement/querystring.test.js @@ -124,14 +124,16 @@ describe("parseQueryString", () => { // Case 8: Throws an error when given a string with missing key or value test("throws an error when passed a string with missing key or value", () => { const invalidQueryStrings = [ - ["=valueOnly"], - ["keyOnly="], - ["=valueOnly&key2=value2"], - ["key1=value1&=valueOnly"], + "=valueOnly", + "keyOnly=", + "=valueOnly&key2=value2", + "key1=value1&=valueOnly", ]; invalidQueryStrings.forEach((input) => { - expect(() => parseQueryString(input)).toThrow(); + expect(() => parseQueryString(input)).toThrow( + "String must contain both a key and a value separated by '='" + ); }); }); }); From a62b0eb7a17943cc6759f503709110c4f9ccc2da Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Sat, 28 Mar 2026 23:02:48 +0200 Subject: [PATCH 50/56] Ensure that neither the key nor value is made up entirely of whitespace --- Sprint-2/implement/querystring.js | 5 +++++ Sprint-2/implement/querystring.test.js | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 7950efac0..6c5b56ab7 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -21,6 +21,11 @@ function parseQueryString(queryString) { const key = pair.slice(0, separatorIndex).trim(); const value = pair.slice(separatorIndex + 1).trim(); + + if (key.length === 0 || value.length === 0) { + throw new Error("Neither the key nor value can be purely whitespace"); + } + queryParams[key] = value; } diff --git a/Sprint-2/implement/querystring.test.js b/Sprint-2/implement/querystring.test.js index 0c17ce340..1cce8e7e9 100644 --- a/Sprint-2/implement/querystring.test.js +++ b/Sprint-2/implement/querystring.test.js @@ -136,4 +136,20 @@ describe("parseQueryString", () => { ); }); }); + + // Case 9: Throws an error when given a string with missing key or value and spaces in '=' separator + test("throws an error when passed a string with missing key or value and spaces in '=' separator", () => { + const invalidQueryStringsWithSpaces = [ + " =valueOnly", + "keyOnly= ", + " =valueOnly&key2=value2", + "key1=value1& =valueOnly", + ]; + + invalidQueryStringsWithSpaces.forEach((input) => { + expect(() => parseQueryString(input)).toThrow( + "Neither the key nor value can be purely whitespace" + ); + }); + }); }); From 35e98cc4f5bce11689141a789b0829c149f81d57 Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Sat, 28 Mar 2026 23:57:14 +0200 Subject: [PATCH 51/56] Fix: correct implementation and test --- Sprint-2/interpret/invert.js | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/Sprint-2/interpret/invert.js b/Sprint-2/interpret/invert.js index bb353fb1f..5bca55a57 100644 --- a/Sprint-2/interpret/invert.js +++ b/Sprint-2/interpret/invert.js @@ -1,29 +1,35 @@ -// 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; } +console.assert( + JSON.stringify(invert({ a: 1 })) === JSON.stringify({ 1: "a" }), + "Test case 1 failed" +); +console.assert( + JSON.stringify(invert({ a: 1, b: 2 })) === JSON.stringify({ 1: "a", 2: "b" }), + "Test case 2 failed" +); + // a) What is the current return value when invert is called with { a : 1 } +// { key: 1 } // b) What is the current return value when invert is called with { a: 1, b: 2 } +// { key: 2 } // c) What is the target return value when invert is called with {a : 1, b: 2} +// { key: 2 } // c) What does Object.entries return? Why is it needed in this program? +// Object.entries returns an array of key-value pairs from the object. // d) Explain why the current return value is different from the target output +// It incorrectly assigns 'key' // e) Fix the implementation of invert (and write tests to prove it's fixed!) From a2eb57fd437dc3205613c884f08d5741ee0e9bc2 Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin Date: Sun, 29 Mar 2026 00:12:54 +0200 Subject: [PATCH 52/56] Revert to main for files outside Sprint 2 --- Sprint-1/fix/median.js | 22 ++--- Sprint-1/implement/dedupe.js | 11 +-- Sprint-1/implement/dedupe.test.js | 124 ++++--------------------- Sprint-1/implement/max.js | 6 +- Sprint-1/implement/max.test.js | 147 +++++++----------------------- Sprint-1/implement/sum.js | 10 +- Sprint-1/implement/sum.test.js | 142 ++++++----------------------- Sprint-1/package-lock.json | 2 - Sprint-1/refactor/includes.js | 9 +- 9 files changed, 96 insertions(+), 377 deletions(-) diff --git a/Sprint-1/fix/median.js b/Sprint-1/fix/median.js index 3a19194bc..b22590bc6 100644 --- a/Sprint-1/fix/median.js +++ b/Sprint-1/fix/median.js @@ -1,18 +1,14 @@ -function calculateMedian(list) { - if (!Array.isArray(list)) return null; - - const numericValues = list.filter((val) => typeof val === "number"); - if (numericValues.length === 0) return null; +// Fix this implementation +// Start by running the tests for this function +// If you're in the Sprint-1 directory, you can run `npm test -- fix` to run the tests in the fix directory - const sortedList = numericValues.toSorted((a, b) => a - b); - const middleIndex = Math.floor(sortedList.length / 2); - let median; +// Hint: Please consider scenarios when 'list' doesn't have numbers (the function is expected to return null) +// or 'list' has mixed values (the function is expected to sort only numbers). - if (sortedList.length % 2 === 1) { - median = sortedList[middleIndex]; - } else { - median = (sortedList[middleIndex - 1] + sortedList[middleIndex]) / 2; - } +function calculateMedian(list) { + const middleIndex = Math.floor(list.length / 2); + const median = list.splice(middleIndex, 1)[0]; return median; } + module.exports = calculateMedian; diff --git a/Sprint-1/implement/dedupe.js b/Sprint-1/implement/dedupe.js index de2736e95..781e8718a 100644 --- a/Sprint-1/implement/dedupe.js +++ b/Sprint-1/implement/dedupe.js @@ -1,10 +1 @@ -function dedupe(list) { - if (!Array.isArray(list)) - throw new Error("Please input an array e.g. [1, 3, 6]"); - - if (list.length === 0) return []; - - return [...new Set(list)]; -} - -module.exports = dedupe; +function dedupe() {} diff --git a/Sprint-1/implement/dedupe.test.js b/Sprint-1/implement/dedupe.test.js index d909e43d8..3a7fc548f 100644 --- a/Sprint-1/implement/dedupe.test.js +++ b/Sprint-1/implement/dedupe.test.js @@ -1,112 +1,28 @@ const dedupe = require("./dedupe.js"); +/* +Dedupe Array -describe("dedupe", () => { - // Case 1: Return an empty array when input is [] - test("given an empty array, it returns an empty array", () => { - expect(dedupe([])).toEqual([]); - }); +📖 Dedupe means **deduplicate** - // Case 2: Return original array where there are no duplicates - test("given an array without duplicates, it returns the original", () => { - const noDuplicates = [ - { input: [4, 2, 1, 3], expected: [4, 2, 1, 3] }, - { input: [3, 1, 2], expected: [3, 1, 2] }, - { input: ["5", "1", "3", "4", "2"], expected: ["5", "1", "3", "4", "2"] }, - { - input: ["6", "1", "5", "3", "2", "4"], - expected: ["6", "1", "5", "3", "2", "4"], - }, - { input: [110, "20", 0], expected: [110, "20", 0] }, - { - input: [6, "five", 2, "apple", 14], - expected: [6, "five", 2, "apple", 14], - }, - ]; +In this kata, you will need to deduplicate the elements of an array - noDuplicates.forEach(({ input, expected }) => { - try { - expect(dedupe(input)).toEqual(expected); - } catch (error) { - throw new Error( - `Failed to return the original [${input}] when no duplicates are present: ${error.message}` - ); - } - }); - }); +E.g. dedupe(['a','a','a','b','b','c']) returns ['a','b','c'] +E.g. dedupe([5, 1, 1, 2, 3, 2, 5, 8]) returns [5, 1, 2, 3, 8] +E.g. dedupe([1, 2, 1]) returns [1, 2] +*/ - // Case 3: Return array with duplicates removed when input has duplicates - test("given an array with duplicates, it returns unique items", () => { - const withDuplicates = [ - { input: [3, 1, 1, 2], expected: [3, 1, 2] }, - { input: [5, 1, 5, 3, 5, 4, 2], expected: [5, 1, 3, 4, 2] }, - { input: ["4", "2", "4", "1", "2", "3"], expected: ["4", "2", "1", "3"] }, - { - input: ["six", "one", "5", "six", "3", "six", "2", "5", "4"], - expected: ["six", "one", "5", "3", "2", "4"], - }, - { - input: [110, "20", 110, "zero", "zero"], - expected: [110, "20", "zero"], - }, - { input: [6, 5, 2, 6, 5, 2], expected: [6, 5, 2] }, - ]; +// Acceptance Criteria: - withDuplicates.forEach(({ input, expected }) => { - try { - expect(dedupe(input)).toEqual(expected); - } catch (error) { - throw new Error( - `Failed to remove duplicates from input [${input}]: ${error.message}` - ); - } - }); - }); +// Given an empty array +// When passed to the dedupe function +// Then it should return an empty array +test.todo("given an empty array, it returns an empty array"); - // Case 4: Throw an error if an array isn't used - test("given a non-array input, it throws an error", () => { - const notArrays = [ - "hello world", - "", - null, - -5, - undefined, - {}, - Infinity, - true, - NaN, - ]; +// Given an array with no duplicates +// When passed to the dedupe function +// Then it should return a copy of the original array - notArrays.forEach((notArray) => { - try { - expect(() => { - dedupe(notArray); - }).toThrow(); - } catch (error) { - throw new Error( - `Failed to throw an error for ${JSON.stringify(notArray)} (${typeof notArray}): ${error.message}` - ); - } - }); - }); - - // Case 5: Don't modify the input array - test("doesn't modify the input array", () => { - const inputArrays = [ - [3, 1, 1, 2], - ["4", "2", "4", "1", "2", "3"], - [6, "five", 2, "apple", 14], - [110, undefined, Infinity, {}, "zero", true], - ]; - inputArrays.forEach((inputArray) => { - try { - const original = [...inputArray]; - dedupe(inputArray); - expect(inputArray).toEqual(original); - } catch (error) { - throw new Error( - `Failed to keep the original input array [${inputArray}] unmutated: ${error.message}` - ); - } - }); - }); -}); +// Given an array of strings or numbers +// When passed to the dedupe function +// Then it should return a new array with duplicates removed while preserving the +// first occurrence of each element from the original array. diff --git a/Sprint-1/implement/max.js b/Sprint-1/implement/max.js index d3308a041..69ca11e9c 100644 --- a/Sprint-1/implement/max.js +++ b/Sprint-1/implement/max.js @@ -1,7 +1,3 @@ -function findMax(elements) { - const numbersOnly = elements.filter((element) => Number.isFinite(element)); - - return numbersOnly.reduce((a, b) => Math.max(a, b), -Infinity); -} +function findMax(elements) {} module.exports = findMax; diff --git a/Sprint-1/implement/max.test.js b/Sprint-1/implement/max.test.js index 08f98de08..82f18fd88 100644 --- a/Sprint-1/implement/max.test.js +++ b/Sprint-1/implement/max.test.js @@ -1,128 +1,43 @@ -const findMax = require("./max.js"); - -describe("findMax", () => { - // Case 1: Given an empty array, it returns -Infinity - test("given an empty array, returns -Infinity", () => { - expect(findMax([])).toEqual(-Infinity); - }); +/* Find the maximum element of an array of numbers - // Case 2: Given an array with one number, it returns that number - test("given an array with one number, it returns that number", () => { - const singleNumbers = [0, 1, 6, 10, 100, 545465, -999, -78]; +In this kata, you will need to implement a function that find the largest numerical element of an array. - singleNumbers.forEach((num) => { - try { - const expected = num; - expect(findMax([num])).toEqual(expected); - } catch (error) { - throw new Error( - `Failed to return ${num} when given an array with a single number: ${error.message}` - ); - } - }); - }); +E.g. max([30, 50, 10, 40]), target output: 50 +E.g. max(['hey', 10, 'hi', 60, 10]), target output: 60 (sum ignores any non-numerical elements) - // Case 3: Given an array with both positive and negative numbers, it returns the largest number overall - test("given an array both positive and negative numbers, it returns the largest number", () => { - const posAndNegs = [ - { input: [3, -4], expected: 3 }, - { input: [1, 54, -5, -3], expected: 54 }, - { input: [-40, -2, -1, 3], expected: 3 }, - { input: [-6, -1, -5, -3, -2, -4], expected: -1 }, - { input: [785, 0, -999], expected: 785 }, - ]; +You should implement this function in max.js, and add tests for it in this file. - posAndNegs.forEach(({ input, expected }) => { - try { - expect(findMax(input)).toEqual(expected); - } catch (error) { - throw new Error( - `Failed to return the largest number ${expected} when given the array [${input}] containing positive and negative numbers: ${error.message}` - ); - } - }); - }); +We have set things up already so that this file can see your function from the other file. +*/ - // Case 4: Given an array with just negative numbers, returns the closest one to zero - test("given an array with just negative numbers, it returns the number closest to zero", () => { - const negNums = [ - { input: [-1, -2, -3, -4, -5], expected: -1 }, - { input: [-5, -4, -3, -2, -1], expected: -1 }, - { input: [-10, -20, -20, -5, -5], expected: -5 }, - { input: [-100, -1000, -100, -57, -57], expected: -57 }, - { input: [-18, -98, -3, -16, -20000], expected: -3 }, - ]; +const findMax = require("./max.js"); - negNums.forEach(({ input, expected }) => { - try { - expect(findMax(input)).toEqual(expected); - } catch (error) { - throw new Error( - `Failed to return the number closest to zero ${expected} when given the array [${input}] containing only negative numbers: ${error.message}` - ); - } - }); - }); +// Given an empty array +// 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"); - // Case 5: Given an array with decimal numbers returns the largest decimal number - test("given an array with decimal numbers, it returns the largest decimal number", () => { - const decimalNums = [ - { input: [1.5, 2.3, 3.7], expected: 3.7 }, - { input: [0.1, 0.2, 0.3], expected: 0.3 }, - { input: [-1.5, -2.3, -3.7], expected: -1.5 }, - { input: [-6.87, -0.3, -99], expected: -0.3 }, - { input: [-15, -23, 37], expected: 37 }, - ]; +// Given an array with one number +// When passed to the max function +// Then it should return that number - decimalNums.forEach(({ input, expected }) => { - try { - expect(findMax(input)).toEqual(expected); - } catch (error) { - throw new Error( - `Failed to return the largest decimal number ${expected} when given the array [${input}]: ${error.message}` - ); - } - }); - }); +// Given an array with both positive and negative numbers +// When passed to the max function +// Then it should return the largest number overall - // Case 6: Given an array with non-number values, it returns the max and ignore non-numeric values - test("given an array with non-number values, it returns the max and ignore non-numeric values", () => { - const withNonNums = [ - { input: [1, 2, "hello", 3, null, 4, undefined, 5], expected: 5 }, - { input: ["a", "b", 0], expected: 0 }, - { - input: [null, undefined, false, true, -Infinity, NaN, -100], - expected: -100, - }, - ]; +// Given an array with just negative numbers +// When passed to the max function +// Then it should return the closest one to zero - withNonNums.forEach(({ input, expected }) => { - try { - expect(findMax(input)).toEqual(expected); - } catch (error) { - throw new Error( - `Failed to return the expected value ${expected} when given the array [${input}]: ${error.message}` - ); - } - }); - }); +// Given an array with decimal numbers +// When passed to the max function +// Then it should return the largest decimal number - // Case 7: Given an array with only non-number values, it returns the -Infinity - test("given an array with only non-number values, it returns the -Infinity", () => { - const onlyNonNums = [ - { input: ["a", "b", "c"], expected: -Infinity }, - { input: [null, undefined, false], expected: -Infinity }, - { input: [NaN, Infinity, -Infinity], expected: -Infinity }, - ]; +// Given an array with non-number values +// When passed to the max function +// Then it should return the max and ignore non-numeric values - onlyNonNums.forEach(({ input, expected }) => { - try { - expect(findMax(input)).toEqual(expected); - } catch (error) { - throw new Error( - `Failed to return the expected value ${expected} when given the array [${input}]: ${error.message}` - ); - } - }); - }); -}); +// 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 diff --git a/Sprint-1/implement/sum.js b/Sprint-1/implement/sum.js index 590dff770..873b75249 100644 --- a/Sprint-1/implement/sum.js +++ b/Sprint-1/implement/sum.js @@ -1,11 +1,3 @@ -function sum(elements) { - const filteredElements = elements.filter((element) => - Number.isFinite(element) - ); - - return parseFloat( - filteredElements.reduce((total, num) => total + num, 0).toFixed(10) - ); -} +function sum(elements) {} module.exports = sum; diff --git a/Sprint-1/implement/sum.test.js b/Sprint-1/implement/sum.test.js index 6372369f6..38f6f252e 100644 --- a/Sprint-1/implement/sum.test.js +++ b/Sprint-1/implement/sum.test.js @@ -1,126 +1,36 @@ -const sum = require("./sum.js"); - -// Case 1: Given an empty array, it should return 0 -test("given an empty array, returns 0", () => { - expect(sum([])).toEqual(0); -}); +/* Sum the numbers in an array -// Case 2: Given an array with just one number, it should return that number -test("given an array with just one number, returns that number", () => { - try { - const singleNums = [0, 1, 5, 10, 100, -999, -78]; - singleNums.forEach((num) => { - expect(sum([num])).toEqual(num); - }); - } catch (error) { - throw new Error( - `Failed to return ${num} when given an array with just one number: ${error.message}` - ); - } -}); +In this kata, you will need to implement a function that sums the numerical elements of an array -// Case 3: Given an array containing negative numbers, it should return the correct total sum -test("given an array with negative numbers, returns the correct total sum", () => { - const withNegs = [ - { input: [10, -5], expected: 5 }, - { input: [1, 54, -5, -3], expected: 47 }, - { input: [-40, -2, -1, 3], expected: -40 }, - { input: [-6, -1, -5, -3, -2, -4], expected: -21 }, - { input: [785, 0, -999], expected: -214 }, - ]; +E.g. sum([10, 20, 30]), target output: 60 +E.g. sum(['hey', 10, 'hi', 60, 10]), target output: 80 (ignore any non-numerical elements) +*/ - withNegs.forEach(({ input, expected }) => { - try { - expect(sum(input)).toEqual(expected); - } catch (error) { - throw new Error( - `Failed to return the correct total sum ${expected} when given the array [${input}] containing negative numbers: ${error.message}` - ); - } - }); -}); - -// Case 4: Given an array with decimal/float numbers, it should return the correct total sum -test("given an array with decimal/float numbers, returns the correct total sum", () => { - const withFloats = [ - { input: [1.5, 2.7, 3.8], expected: 8 }, - { input: [-1.5, -2.7, -3.8], expected: -8 }, - { input: [1.5, -2.7, 3.8], expected: 2.6 }, - { input: [-1.5, 2.7, -3.8], expected: -2.6 }, - ]; +const sum = require("./sum.js"); - withFloats.forEach(({ input, expected }) => { - try { - expect(sum(input)).toEqual(expected); - } catch (error) { - throw new Error( - `Failed to return the correct total sum ${expected} when given the array [${input}] containing decimal/float numbers: ${error.message}` - ); - } - }); -}); +// Acceptance Criteria: -// Case 5: Given an array containing non-number values, it should return the sum of the numerical elements and ignore non-numerical values -test("given an array with non-number values, it returns the sum of the numerical elements and ignores non-numerical values", () => { - const withNonNumbers = [ - { input: ["hey", 10, "hi", 60, 10], expected: 80 }, - { input: ["hello", "world"], expected: 0 }, - { input: [1, 2, "three", 4, "five"], expected: 7 }, - { input: [null, undefined, false, true], expected: 0 }, - { input: [1.5, "two", 3.5], expected: 5 }, - ]; +// Given an empty array +// When passed to the sum function +// Then it should return 0 +test.todo("given an empty array, returns 0"); - withNonNumbers.forEach(({ input, expected }) => { - try { - expect(sum(input)).toEqual(expected); - } catch (error) { - throw new Error( - `Failed to return the correct sum ${expected} when given the array [${input}] containing non-number values: ${error.message}` - ); - } - }); -}); +// Given an array with just one number +// When passed to the sum function +// Then it should return that number -// Case 6: Given an array with only non-number values, it should return zero -test("given an array with only non-number values, it returns the least surprising value given how it behaves for all other inputs", () => { - const onlyNonNumbers = [ - { input: ["hello", "world"], expected: 0 }, - { input: [null, undefined, false, {}], expected: 0 }, - { input: ["a", "b", null, NaN], expected: 0 }, - { input: [Infinity, []], expected: 0 }, - ]; +// Given an array containing negative numbers +// When passed to the sum function +// Then it should still return the correct total sum - onlyNonNumbers.forEach(({ input, expected }) => { - try { - expect(sum(input)).toEqual(expected); - } catch (error) { - throw new Error( - `Failed to return the least surprising value ${expected} when given the array [${input}] containing only non-number values: ${error.message}` - ); - } - }); -}); +// Given an array with decimal/float numbers +// When passed to the sum function +// Then it should return the correct total sum -// Case 7: Given an array with non-number values, it should return the sum of the numerical elements and ignore non-numerical values -test("given an array with non-number values, it returns the sum of the numerical elements and ignores non-numerical values", () => { - const withNonNumbers = [ - { input: ["hey", 10, "hi", 60, 10], expected: 80 }, - { input: ["hello", "world", -10, -30, 10], expected: -30 }, - { input: [1, 20, "three", 4, "five", NaN], expected: 25 }, - { - input: [null, undefined, false, Infinity, 2.5, -12, -1.5], - expected: -11, - }, - { input: [1.5, "two", 3.5], expected: 5 }, - ]; +// 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 - withNonNumbers.forEach(({ input, expected }) => { - try { - expect(sum(input)).toEqual(expected); - } catch (error) { - throw new Error( - `Failed to return the correct sum ${expected} when given the array [${input}] containing non-number values: ${error.message}` - ); - } - }); -}); +// 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 diff --git a/Sprint-1/package-lock.json b/Sprint-1/package-lock.json index b52480af5..83e427d0b 100644 --- a/Sprint-1/package-lock.json +++ b/Sprint-1/package-lock.json @@ -56,7 +56,6 @@ "integrity": "sha512-Oixnb+DzmRT30qu9d3tJSQkxuygWm32DFykT4bRoORPa9hZ/L4KhVB/XiRm6KG+roIEM7DBQlmg27kw2HZkdZg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.25.7", @@ -1369,7 +1368,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001663", "electron-to-chromium": "^1.5.28", diff --git a/Sprint-1/refactor/includes.js b/Sprint-1/refactor/includes.js index a2ed75359..29dad81f0 100644 --- a/Sprint-1/refactor/includes.js +++ b/Sprint-1/refactor/includes.js @@ -1,6 +1,11 @@ +// Refactor the implementation of includes to use a for...of loop + function includes(list, target) { - for (const item of list) { - if (item === target) return true; + for (let index = 0; index < list.length; index++) { + const element = list[index]; + if (element === target) { + return true; + } } return false; } From 7c8f06d108cde763dbdfe75022be0c605ceb2ee0 Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin <59330021+bytesandroses@users.noreply.github.com> Date: Sun, 29 Mar 2026 00:29:20 +0200 Subject: [PATCH 53/56] Implement findMax function in max.js --- Sprint-1/implement/max.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sprint-1/implement/max.js b/Sprint-1/implement/max.js index 69ca11e9c..2f0bb9c87 100644 --- a/Sprint-1/implement/max.js +++ b/Sprint-1/implement/max.js @@ -1,3 +1,5 @@ -function findMax(elements) {} +function findMax(elements) { + +} module.exports = findMax; From a005979c9fa377d0f681dbabed278fa16e338640 Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin <59330021+bytesandroses@users.noreply.github.com> Date: Sun, 29 Mar 2026 00:29:46 +0200 Subject: [PATCH 54/56] Implement findMax function in max.js Implement the findMax function to determine the maximum value in an array. --- Sprint-1/implement/max.js | 1 - 1 file changed, 1 deletion(-) diff --git a/Sprint-1/implement/max.js b/Sprint-1/implement/max.js index 2f0bb9c87..6dd76378e 100644 --- a/Sprint-1/implement/max.js +++ b/Sprint-1/implement/max.js @@ -1,5 +1,4 @@ function findMax(elements) { - } module.exports = findMax; From e5aee85f95cb0c50cb0705af46399d499b52a3ab Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin <59330021+bytesandroses@users.noreply.github.com> Date: Sun, 29 Mar 2026 00:30:08 +0200 Subject: [PATCH 55/56] Add sum function definition in sum.js --- Sprint-1/implement/sum.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sprint-1/implement/sum.js b/Sprint-1/implement/sum.js index 873b75249..9062aafe3 100644 --- a/Sprint-1/implement/sum.js +++ b/Sprint-1/implement/sum.js @@ -1,3 +1,4 @@ -function sum(elements) {} +function sum(elements) { +} module.exports = sum; From b21245720f5195e68abda11038dd491107657123 Mon Sep 17 00:00:00 2001 From: Isaac Abodunrin <59330021+bytesandroses@users.noreply.github.com> Date: Sun, 29 Mar 2026 00:30:44 +0200 Subject: [PATCH 56/56] Fix formatting in sum.test.js --- Sprint-1/implement/sum.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sprint-1/implement/sum.test.js b/Sprint-1/implement/sum.test.js index 38f6f252e..dd0a090ca 100644 --- a/Sprint-1/implement/sum.test.js +++ b/Sprint-1/implement/sum.test.js @@ -13,7 +13,7 @@ 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") // Given an array with just one number // When passed to the sum function