From dd734e285cf7e00060c564658a77770eca3b8b6a Mon Sep 17 00:00:00 2001 From: Zadri Abdule Date: Tue, 7 Jul 2026 00:33:16 +0100 Subject: [PATCH 01/16] updated fix/median.js to make tests pass --- Sprint-1/fix/median.js | 31 +++++++++++++++++++++++++++++-- Sprint-1/fix/median.test.js | 22 +++++++++++++++++----- Sprint-1/implement/dedupe.test.js | 2 +- Sprint-1/implement/max.js | 3 +-- Sprint-1/implement/sum.js | 3 +-- Sprint-1/implement/sum.test.js | 2 +- Sprint-1/jest.config.js | 9 +++++++++ 7 files changed, 59 insertions(+), 13 deletions(-) create mode 100644 Sprint-1/jest.config.js diff --git a/Sprint-1/fix/median.js b/Sprint-1/fix/median.js index b22590bc6..674fc4587 100644 --- a/Sprint-1/fix/median.js +++ b/Sprint-1/fix/median.js @@ -6,8 +6,35 @@ // or 'list' has mixed values (the function is expected to sort only numbers). function calculateMedian(list) { - const middleIndex = Math.floor(list.length / 2); - const median = list.splice(middleIndex, 1)[0]; + // Return null when input is not an array (tests expect null for invalid input) + if (!Array.isArray(list)) { + return null; + } + + // Keep only actual numbers. This filters out strings, null, undefined, objects, etc. + // We deliberately do not coerce numeric strings (e.g. "3") to numbers — tests expect + // non-number values to be ignored rather than parsed. + const numericList = list.filter((item) => typeof item === "number" && !isFinite(item)); + + // If there are no numeric values, return null (the function should not attempt + // to compute a median from non-numeric or empty input). + if (numericList.length === 0) { + return null; + } + + // Sort numerically. Using slice() first ensures we don't mutate the filtered + // array in case the original caller relies on references; the tests also assert + // the original `list` should not be modified. + const sorted = numericList.slice().sort((a, b) => a - b); + + // Compute middle index. For odd lengths the middle element is the median. + // For even lengths the median is the average of the two middle elements. + const middleIndex = Math.floor(sorted.length / 2); + const median = sorted.length % 2 === 1 + ? sorted[middleIndex] + : (sorted[middleIndex - 1] + sorted[middleIndex]) / 2; + + // Return the computed median (number). return median; } diff --git a/Sprint-1/fix/median.test.js b/Sprint-1/fix/median.test.js index 21da654d7..c262c3776 100644 --- a/Sprint-1/fix/median.test.js +++ b/Sprint-1/fix/median.test.js @@ -13,7 +13,8 @@ describe("calculateMedian", () => { { input: [1, 2, 3, 4], expected: 2.5 }, { input: [1, 2, 3, 4, 5, 6], expected: 3.5 }, ].forEach(({ input, expected }) => - it(`returns the median for [${input}]`, () => expect(calculateMedian(input)).toEqual(expected)) + it(`returns the median for [${input}]`, () => + expect(calculateMedian(input)).toEqual(expected)) ); [ @@ -24,7 +25,8 @@ describe("calculateMedian", () => { { input: [110, 20, 0], expected: 20 }, { input: [6, -2, 2, 12, 14], expected: 6 }, ].forEach(({ input, expected }) => - it(`returns the correct median for unsorted array [${input}]`, () => expect(calculateMedian(input)).toEqual(expected)) + it(`returns the correct median for unsorted array [${input}]`, () => + expect(calculateMedian(input)).toEqual(expected)) ); it("doesn't modify the input array [3, 1, 2]", () => { @@ -33,8 +35,17 @@ describe("calculateMedian", () => { expect(list).toEqual([3, 1, 2]); }); - [ 'not an array', 123, null, undefined, {}, [], ["apple", null, undefined] ].forEach(val => - it(`returns null for non-numeric array (${val})`, () => expect(calculateMedian(val)).toBe(null)) + [ + "not an array", + 123, + null, + undefined, + {}, + [], + ["apple", null, undefined], + ].forEach((val) => + it(`returns null for non-numeric array (${val})`, () => + expect(calculateMedian(val)).toBe(null)) ); [ @@ -45,6 +56,7 @@ describe("calculateMedian", () => { { input: [3, "apple", 1, null, 2, undefined, 4], expected: 2.5 }, { input: ["banana", 5, 3, "apple", 1, 4, 2], expected: 3 }, ].forEach(({ input, expected }) => - it(`filters out non-numeric values and calculates the median for [${input}]`, () => expect(calculateMedian(input)).toEqual(expected)) + it(`filters out non-numeric values and calculates the median for [${input}]`, () => + expect(calculateMedian(input)).toEqual(expected)) ); }); diff --git a/Sprint-1/implement/dedupe.test.js b/Sprint-1/implement/dedupe.test.js index d7c8e3d8e..3a7fc548f 100644 --- a/Sprint-1/implement/dedupe.test.js +++ b/Sprint-1/implement/dedupe.test.js @@ -24,5 +24,5 @@ test.todo("given an empty array, it returns an empty array"); // 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 +// 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 6dd76378e..69ca11e9c 100644 --- a/Sprint-1/implement/max.js +++ b/Sprint-1/implement/max.js @@ -1,4 +1,3 @@ -function findMax(elements) { -} +function findMax(elements) {} module.exports = findMax; diff --git a/Sprint-1/implement/sum.js b/Sprint-1/implement/sum.js index 9062aafe3..873b75249 100644 --- a/Sprint-1/implement/sum.js +++ b/Sprint-1/implement/sum.js @@ -1,4 +1,3 @@ -function sum(elements) { -} +function sum(elements) {} module.exports = sum; diff --git a/Sprint-1/implement/sum.test.js b/Sprint-1/implement/sum.test.js index dd0a090ca..38f6f252e 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 diff --git a/Sprint-1/jest.config.js b/Sprint-1/jest.config.js new file mode 100644 index 000000000..079d3cdb1 --- /dev/null +++ b/Sprint-1/jest.config.js @@ -0,0 +1,9 @@ +module.exports = { + // Treat Sprint-1 as the root for Jest when running from this package + rootDir: '.', + // Only match tests inside the `fix` folder (you can expand this if needed) + testMatch: ['/fix/**/*.test.js'], + // Ignore node_modules inside Sprint-1 + testPathIgnorePatterns: ['/node_modules/'], + modulePathIgnorePatterns: ['/node_modules/'], +}; From 135bf30b2449ca59fe14c860ee212e988f75d102 Mon Sep 17 00:00:00 2001 From: Zadri Abdule Date: Wed, 8 Jul 2026 15:47:34 +0100 Subject: [PATCH 02/16] added dedupe implementation and tests --- Sprint-1/fix/median.js | 3 +- Sprint-1/implement/dedupe.js | 58 ++++++++++++++++++++++++++++++- Sprint-1/implement/dedupe.test.js | 54 ++++++++++++++++++++++++++-- Sprint-1/jest.config.js | 5 +-- 4 files changed, 112 insertions(+), 8 deletions(-) diff --git a/Sprint-1/fix/median.js b/Sprint-1/fix/median.js index 674fc4587..f8d868fca 100644 --- a/Sprint-1/fix/median.js +++ b/Sprint-1/fix/median.js @@ -4,7 +4,6 @@ // 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). - function calculateMedian(list) { // Return null when input is not an array (tests expect null for invalid input) if (!Array.isArray(list)) { @@ -14,7 +13,7 @@ function calculateMedian(list) { // Keep only actual numbers. This filters out strings, null, undefined, objects, etc. // We deliberately do not coerce numeric strings (e.g. "3") to numbers — tests expect // non-number values to be ignored rather than parsed. - const numericList = list.filter((item) => typeof item === "number" && !isFinite(item)); + const numericList = list.filter((item) => typeof item === "number" && Number.isFinite(item)); // If there are no numeric values, return null (the function should not attempt // to compute a median from non-numeric or empty input). diff --git a/Sprint-1/implement/dedupe.js b/Sprint-1/implement/dedupe.js index 781e8718a..08a244934 100644 --- a/Sprint-1/implement/dedupe.js +++ b/Sprint-1/implement/dedupe.js @@ -1 +1,57 @@ -function dedupe() {} +/* + notes: + + - One-line summary: return a new array with duplicates removed, preserving + the order of first occurrences. + - Input handling: null or undefined -> return [], any other non-array -> throw. + - Key idea: use a Set to track values we've seen; push unseen items to result. + + Quick checks: + 1) If nothing changes, test with: node -e "console.log(require('./implement/dedupe')([1,1,2]))" + 2) If order looks wrong, remember we preserve first occurrences. + 3) If unexpected equality (objects), remember objects are compared by reference. + + Tiny TODOs (one small step each): + - [ ] Consider accepting array-like objects? (optional) + - [ ] Decide whether to treat non-array values consistently (return [] vs throw) + + Short examples: + dedupe([1,2,1]) -> [1,2] + dedupe(null) -> [] + dedupe([{a:1},{a:1}]) -> [{a:1},{a:1}] // different refs stay, not deduped by shape + + Implementation (no behavior changes below): +*/ + +function dedupe(arr) { + // Quick guard: treat null/undefined as empty lists — doesn't throw, just returns [] + if (arr === null || arr === undefined) { + return []; + } + + // If it's not an array, fail fast — makes bugs obvious (helps debugging) + if (!Array.isArray(arr)) { + throw new TypeError("Input must be an array"); + } + + // seen: keep track of values we've already added (O(1) lookup) + const seen = new Set(); + // result: build new array, preserve first-seen order + const result = []; + + // iterate in order; if unseen -> keep it + for (const item of arr) { + if (!seen.has(item)) { + seen.add(item); + result.push(item); + } + } + + // return the deduped array + return result; +} + +module.exports = dedupe; + + + diff --git a/Sprint-1/implement/dedupe.test.js b/Sprint-1/implement/dedupe.test.js index 3a7fc548f..5f89586a5 100644 --- a/Sprint-1/implement/dedupe.test.js +++ b/Sprint-1/implement/dedupe.test.js @@ -1,4 +1,4 @@ -const dedupe = require("./dedupe.js"); + /* Dedupe Array @@ -16,13 +16,61 @@ E.g. dedupe([1, 2, 1]) returns [1, 2] // 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"); + +const dedupe = require("./dedupe.js"); + +describe("dedupe", () => { + test("given an empty array, it returns an empty array", () => { + expect(dedupe([])).toEqual([]); + }); +}); + +test("throws for non-array input", () => { + expect(() => dedupe(42)).toThrow(TypeError); + expect(() => dedupe("string")).toThrow(TypeError); + expect(() => dedupe({})).toThrow(TypeError); +}); + +test("does not mutate the input array", () => { + const input = [1, 2, 3]; + dedupe(input); + expect(input).toEqual([1, 2, 3]); +}); + +test("objects are compared by reference", () => { + const a = { a: 1 }; + const b = { a: 1 }; + expect(dedupe([a, b, a])).toEqual([a, b]); +}); + +test('null or undefined returns empty array', () => { + expect(dedupe(null)).toEqual([]); + expect(dedupe(undefined)).toEqual([]); +}); // Given an array with no duplicates // When passed to the dedupe function // Then it should return a copy of the original array - +test("given an array with no duplicates, it returns a copy of the original array", () => { + expect(dedupe(['a', 'b', 'c'])).toEqual(['a', 'b', 'c']); + expect(dedupe([5, 1, 2, 3, 8])).toEqual([5, 1, 2, 3, 8]); + expect(dedupe([1, 2])).toEqual([1, 2]); + expect(dedupe(['apple', 'banana', 'orange'])).toEqual(['apple', 'banana', 'orange']); + expect(dedupe(['Monday', 'Tuesday', 'Wednesday'])).toEqual(['Monday', 'Tuesday', 'Wednesday']); +}); // 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. +test("given an array of strings or numbers, it returns a new array with duplicates removed while preserving the first occurrence of each element", () => { + expect(dedupe(['a', 'b', 'a', 'c', 'b'])).toEqual(['a', 'b', 'c']); + expect(dedupe([1, 2, 1, 3, 2])).toEqual([1, 2, 3]); + expect(dedupe([1, 2, 1])).toEqual([1, 2]); + expect(dedupe(['apple', 'banana', 'apple', 'orange', 'banana'])).toEqual(['apple', 'banana', 'orange']); + expect(dedupe([true, false, true])).toEqual([true, false]); +}); + +test("given an array with mixed types, it returns a new array with duplicates removed while preserving the first occurrence", () => { + expect(dedupe([1, '1', 2, '2', 1])).toEqual([1, '1', 2, '2']); + expect(dedupe(['apple', 1, 'apple', 2])).toEqual(['apple', 1, 2]); +}); diff --git a/Sprint-1/jest.config.js b/Sprint-1/jest.config.js index 079d3cdb1..c4036c4eb 100644 --- a/Sprint-1/jest.config.js +++ b/Sprint-1/jest.config.js @@ -1,8 +1,9 @@ module.exports = { // Treat Sprint-1 as the root for Jest when running from this package rootDir: '.', - // Only match tests inside the `fix` folder (you can expand this if needed) - testMatch: ['/fix/**/*.test.js'], + // Match tests inside the `fix` and `implement` folders + // so running `npm test -- fix` will also pick up implement tests like dedupe.test.js + testMatch: ['/fix/**/*.test.js', '/implement/**/*.test.js'], // Ignore node_modules inside Sprint-1 testPathIgnorePatterns: ['/node_modules/'], modulePathIgnorePatterns: ['/node_modules/'], From 91bb6480972f9f2f7733e66c4fe89335ce27bf59 Mon Sep 17 00:00:00 2001 From: Zadri Abdule Date: Wed, 8 Jul 2026 20:03:41 +0100 Subject: [PATCH 03/16] implemented findMax and added tests --- Sprint-1/implement/max.js | 13 ++++++- Sprint-1/implement/max.test.js | 64 ++++++++++++++++++++++++---------- 2 files changed, 57 insertions(+), 20 deletions(-) diff --git a/Sprint-1/implement/max.js b/Sprint-1/implement/max.js index 69ca11e9c..79f06667c 100644 --- a/Sprint-1/implement/max.js +++ b/Sprint-1/implement/max.js @@ -1,3 +1,14 @@ -function findMax(elements) {} +function findMax(elements) { + let max = -Infinity; + for (const value of elements) { + if (typeof value === "number") { + max = Math.max(max, value); + } + } + return max; +} + + + module.exports = findMax; diff --git a/Sprint-1/implement/max.test.js b/Sprint-1/implement/max.test.js index 82f18fd88..a2a041fce 100644 --- a/Sprint-1/implement/max.test.js +++ b/Sprint-1/implement/max.test.js @@ -16,28 +16,54 @@ const findMax = require("./max.js"); // When passed to the max function // Then it should return -Infinity // Delete this test.todo and replace it with a test. -test.todo("given an empty array, returns -Infinity"); +describe("findMax", () => { + test("should find the maximum positive number", () => { + expect(findMax([1, 2, 3, 4, 5])).toBe(5); + }); -// Given an array with one number -// When passed to the max function -// Then it should return that number + test("should return -Infinity if array contains no numbers", () => { + expect(findMax(['a', 'b', 'c'])).toBe(-Infinity); + }); -// Given an array with both positive and negative numbers -// When passed to the max function -// Then it should return the largest number overall + // Given an array with one number + // When passed to the max function + // Then it should return that number + test("should handle an array with a single number", () => { + expect(findMax([42])).toBe(42); + }); -// Given an array with just negative numbers -// When passed to the max function -// Then it should return the closest one to zero + // Given an array with both positive and negative numbers + // When passed to the max function + // Then it should return the largest number overall + test("should find the maximum number in a mixed array", () => { + expect(findMax([-10, 0, 10, 20])).toBe(20); + }); -// Given an array with decimal numbers -// When passed to the max function -// Then it should return the largest decimal number + // Given an array with just negative numbers + // When passed to the max function + // Then it should return the closest one to zero + test("should work correctly with negative numbers", () => { + expect(findMax([-10, -20, -30])).toBe(-10); + }); -// Given an array with non-number values -// When passed to the max function -// Then it should return the max and ignore non-numeric values + // Given an array with decimal numbers + // When passed to the max function + // Then it should return the largest decimal number + test("should work correctly with decimal numbers", () => { + expect(findMax([1.5, 2.5, 3.5])).toBe(3.5); + }); -// 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 + // Given an array with non-number values + // When passed to the max function + // Then it should return the max and ignore non-numeric values + test("should ignore non-number values" , () => { + expect(findMax(["a", "b", "c", 40])).toBe(40); + }); + + // Given an array with only non-number values + // When passed to the max function + // Then it should return the least surprising value given how it behaves for all other inputs + test("given an array with only non-number values, returns -Infinity", () => { + expect(findMax(["a", "b", "c"])).toBe(-Infinity); + }); +}); From 050521682528872a0f82093af20a229db02781d7 Mon Sep 17 00:00:00 2001 From: Zadri Abdule Date: Wed, 8 Jul 2026 20:41:08 +0100 Subject: [PATCH 04/16] added sum implementation and tests --- Sprint-1/implement/sum.js | 10 +++++++++- Sprint-1/implement/sum.test.js | 26 ++++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/Sprint-1/implement/sum.js b/Sprint-1/implement/sum.js index 873b75249..8638400f5 100644 --- a/Sprint-1/implement/sum.js +++ b/Sprint-1/implement/sum.js @@ -1,3 +1,11 @@ -function sum(elements) {} +function sum(elements) { + let total = 0; + for (let i = 0; i < elements.length; i++) { + if (typeof elements[i] === "number") { + total += elements[i]; + } + } + return total; +} module.exports = sum; diff --git a/Sprint-1/implement/sum.test.js b/Sprint-1/implement/sum.test.js index 38f6f252e..7f4da067a 100644 --- a/Sprint-1/implement/sum.test.js +++ b/Sprint-1/implement/sum.test.js @@ -13,24 +13,46 @@ 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"); +describe("sum", () => { + test("given an empty array, returns 0", () => { + expect(sum([])).toBe(0); + }); + + test("given an array with just one number, returns that number", () => { + expect(sum([42])).toBe(42); + }); +}); // Given an array with just one number // When passed to the sum function // Then it should return that number - +test("should sum an array of positive numbers", () => { + expect(sum([1, 2, 3, 4, 5])).toBe(15); +}); // Given an array containing negative numbers // When passed to the sum function // Then it should still return the correct total sum +test("should sum an array containing negative numbers", () => { + expect(sum([-1, -2, -3, -4, -5])).toBe(-15); +}); // Given an array with decimal/float numbers // When passed to the sum function // Then it should return the correct total sum +test("should sum an array of decimal numbers", () => { + expect(sum([1.5, 2.5, 3.5])).toBe(7.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 +test("should ignore non-number values", () => { + expect(sum(["a", "b", "c", 40])).toBe(40); +}); // Given an array with only non-number values // When passed to the sum function // Then it should return the least surprising value given how it behaves for all other inputs +test("given an array with only non-number values, returns 0", () => { + expect(sum(["a", "b", "c"])).toBe(0); +}); From f1631e72b400a2e31e1733d5fed674b5bbb8e935 Mon Sep 17 00:00:00 2001 From: Zadri Abdule Date: Wed, 8 Jul 2026 20:59:20 +0100 Subject: [PATCH 05/16] refactored includes --- Sprint-1/refactor/includes.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Sprint-1/refactor/includes.js b/Sprint-1/refactor/includes.js index 29dad81f0..8c9ae2e66 100644 --- a/Sprint-1/refactor/includes.js +++ b/Sprint-1/refactor/includes.js @@ -1,8 +1,7 @@ // Refactor the implementation of includes to use a for...of loop function includes(list, target) { - for (let index = 0; index < list.length; index++) { - const element = list[index]; + for (const element of list) { if (element === target) { return true; } From 9c4d4dd6d705ebdd9b6eb759491b75ca932b062c Mon Sep 17 00:00:00 2001 From: Zadri Abdule Date: Wed, 8 Jul 2026 21:38:14 +0100 Subject: [PATCH 06/16] completed aoc 2018 day 1 solutions --- Sprint-1/stretch/aoc-2018-day1/solution.js | 32 ++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/Sprint-1/stretch/aoc-2018-day1/solution.js b/Sprint-1/stretch/aoc-2018-day1/solution.js index e69de29bb..9263787c8 100644 --- a/Sprint-1/stretch/aoc-2018-day1/solution.js +++ b/Sprint-1/stretch/aoc-2018-day1/solution.js @@ -0,0 +1,32 @@ +const fs = require("fs"); +const path = require("path"); + +// Read input relative to this file so script can be run from anywhere +const data = fs.readFileSync(path.join(__dirname, "input.txt"), "utf8"); +const changes = data + .split(/\r?\n/) + .map((s) => s.trim()) + .filter(Boolean) + .map(Number); + +// Part 1: final frequency after one pass +const finalFrequency = changes.reduce((acc, change) => acc + change, 0); + +// Part 2: first repeated cumulative frequency (iterate the list repeatedly) +function findFirstDuplicate(arr) { + const seen = new Set([0]); + let freq = 0; + + while (true) { + for (const change of arr) { + freq += change; + if (seen.has(freq)) return freq; + seen.add(freq); + } + } +} + +const firstDuplicate = findFirstDuplicate(changes); + +console.log("Final frequency (part 1):", finalFrequency); +console.log("First repeated frequency (part 2):", firstDuplicate); From 5f43f4d07db9d53e9041fdd6751511db84b1946c Mon Sep 17 00:00:00 2001 From: Zadri Abdule Date: Thu, 9 Jul 2026 00:13:55 +0100 Subject: [PATCH 07/16] debuged address.js --- Sprint-2/debug/address.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sprint-2/debug/address.js b/Sprint-2/debug/address.js index 940a6af83..36d2f865d 100644 --- a/Sprint-2/debug/address.js +++ b/Sprint-2/debug/address.js @@ -12,4 +12,4 @@ const address = { postcode: "XYZ 123", }; -console.log(`My house number is ${address[0]}`); +console.log(`My house number is ${address.houseNumber}`); From a282d02e2ade8f042789802bbef419b4481949b4 Mon Sep 17 00:00:00 2001 From: Zadri Abdule Date: Thu, 9 Jul 2026 00:17:45 +0100 Subject: [PATCH 08/16] debuged address.js --- Sprint-2/debug/address.js | 1 + 1 file changed, 1 insertion(+) diff --git a/Sprint-2/debug/address.js b/Sprint-2/debug/address.js index 36d2f865d..5581abff1 100644 --- a/Sprint-2/debug/address.js +++ b/Sprint-2/debug/address.js @@ -1,4 +1,5 @@ // Predict and explain first... +// that it will throw a undefined error because the address object is not defined in the code snippet. The code snippet is missing the definition of the address object, which is necessary for the console.log statement to work correctly. // This code should log out the houseNumber from the address object // but it isn't working... From 3699cc2a909bae90f4a7c9d5d3dc1b0d62beabdb Mon Sep 17 00:00:00 2001 From: Zadri Abdule Date: Thu, 9 Jul 2026 00:41:50 +0100 Subject: [PATCH 09/16] debuged author.js and added prediction --- Sprint-2/debug/author.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sprint-2/debug/author.js b/Sprint-2/debug/author.js index 8c2125977..8700730d3 100644 --- a/Sprint-2/debug/author.js +++ b/Sprint-2/debug/author.js @@ -1,4 +1,5 @@ // Predict and explain first... +// will throw a TypeError because Object.values() is not a function. The correct way to get the values of an object is to use Object.values(author) instead of Object.values(author). The code is trying to call Object.values() on the author object, which is not a function, hence the error. // This program attempts to log out all the property values in the object. // But it isn't working. Explain why first and then fix the problem @@ -11,6 +12,6 @@ const author = { alive: true, }; -for (const value of author) { +for (const value of Object.values(author)) { console.log(value); } From 4262b7dec4974ad01571d5f2773c94fffe50d597 Mon Sep 17 00:00:00 2001 From: Zadri Abdule Date: Thu, 9 Jul 2026 00:54:21 +0100 Subject: [PATCH 10/16] debuged recipe.js and added prediction --- Sprint-2/debug/recipe.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Sprint-2/debug/recipe.js b/Sprint-2/debug/recipe.js index 6cbdd22cd..3303e40e0 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -1,4 +1,5 @@ // Predict and explain first... +// will log out the title and how many it serves, but it will not log out the ingredients on new lines. The ingredients are in an array, and the current code does not iterate over that array to log each ingredient on a new line. // This program should log out the title, how many it serves and the ingredients. // Each ingredient should be logged on a new line @@ -10,6 +11,7 @@ const recipe = { ingredients: ["olive oil", "tomatoes", "salt", "pepper"], }; -console.log(`${recipe.title} serves ${recipe.serves} - ingredients: -${recipe}`); +console.log(`${recipe.title} serves ${recipe.serves}`); +for (const ingredient of recipe.ingredients) { + console.log(ingredient); +} From 8a7826948175d27a1c29977fa5c53635d09a255f Mon Sep 17 00:00:00 2001 From: Zadri Abdule Date: Thu, 9 Jul 2026 12:38:23 +0100 Subject: [PATCH 11/16] contains.js - implemention of contains & tests --- Sprint-2/implement/contains.js | 7 ++++++- Sprint-2/implement/contains.test.js | 26 +++++++++++++++++++++++++- Sprint-2/interpret/invert.js | 5 ++++- Sprint-2/interpret/invert.test.js | 13 +++++++++++++ 4 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 Sprint-2/interpret/invert.test.js diff --git a/Sprint-2/implement/contains.js b/Sprint-2/implement/contains.js index cd779308a..dafc64de7 100644 --- a/Sprint-2/implement/contains.js +++ b/Sprint-2/implement/contains.js @@ -1,3 +1,8 @@ -function contains() {} +function contains(obj, prop) { + if (typeof obj !== "object" || obj === null) { + return false; + } + return obj.hasOwnProperty(prop); +} module.exports = contains; diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index 326bdb1f2..917de2fbd 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -16,20 +16,44 @@ as the object doesn't contains a key of 'c' // Given a contains function // When passed an object and a property name // Then it should return true if the object contains the property, false otherwise +describe("contains", () => { + test("should return true if the object contains the property, false otherwise", () => { + expect(contains({ a: 1, b: 2 }, 'a')).toBe(true); + expect(contains({ a: 1, b: 2 }, 'b')).toBe(true); + expect(contains({ a: 1, b: 2 }, 'c')).toBe(false); + expect(contains({}, 'a')).toBe(false); + }); +}); // Given an empty object // When passed to contains // Then it should return false -test.todo("contains on empty object returns false"); +test("contains returns false for empty object", () => { + expect(contains({}, 'a')).toBe(false); +}); + + // Given an object with properties // When passed to contains with an existing property name // Then it should return true +test("contains returns true for existing keys", () => { + expect(contains({ a: 1, b: 2 }, 'a')).toBe(true); + expect(contains({ a: 1, b: 2 }, 'b')).toBe(true); +}); // Given an object with properties // When passed to contains with a non-existent property name // Then it should return false +test("contains returns false for non-existent property name", () => { + expect(contains({ a: 1, b: 2 }, 'c')).toBe(false); +}); // Given invalid parameters like an array // When passed to contains // Then it should return false or throw an error +test("contains with invalid parameters returns false", () => { + expect(contains([], 'a')).toBe(false); + expect(contains(null, 'a')).toBe(false); + expect(contains(undefined, 'a')).toBe(false); +}); diff --git a/Sprint-2/interpret/invert.js b/Sprint-2/interpret/invert.js index bb353fb1f..99ace069f 100644 --- a/Sprint-2/interpret/invert.js +++ b/Sprint-2/interpret/invert.js @@ -10,12 +10,15 @@ function invert(obj) { const invertedObj = {}; for (const [key, value] of Object.entries(obj)) { - invertedObj.key = value; + // use computed property name so the value becomes the new key + invertedObj[String(value)] = key; } return invertedObj; } +module.exports = invert; + // a) What is the current return value when invert is called with { a : 1 } // b) What is the current return value when invert is called with { a: 1, b: 2 } diff --git a/Sprint-2/interpret/invert.test.js b/Sprint-2/interpret/invert.test.js new file mode 100644 index 000000000..d779f1bec --- /dev/null +++ b/Sprint-2/interpret/invert.test.js @@ -0,0 +1,13 @@ +const invert = require('./invert'); + +test('inverts a single key/value pair', () => { + expect(invert({ a: 1 })).toEqual({ '1': 'a' }); +}); + +test('inverts multiple pairs', () => { + expect(invert({ a: 1, b: 2 })).toEqual({ '1': 'a', '2': 'b' }); +}); + +test('string values become keys', () => { + expect(invert({ a: 'x', b: 'y' })).toEqual({ x: 'a', y: 'b' }); +}); From b36df77cb0e81868e54a4a6db1c2b2237a9437ba Mon Sep 17 00:00:00 2001 From: Zadri Abdule Date: Wed, 22 Jul 2026 19:16:37 +0100 Subject: [PATCH 12/16] add implemention and tests for querystring.js --- Sprint-2/implement/lookup.js | 16 +++++++++-- Sprint-2/implement/lookup.test.js | 47 ++++++++++++++++++++++++++++++- Sprint-2/implement/querystring.js | 32 +++++++++++++++------ 3 files changed, 83 insertions(+), 12 deletions(-) diff --git a/Sprint-2/implement/lookup.js b/Sprint-2/implement/lookup.js index a6746e07f..adc25cd72 100644 --- a/Sprint-2/implement/lookup.js +++ b/Sprint-2/implement/lookup.js @@ -1,5 +1,17 @@ -function createLookup() { - // implementation here +function createLookup(countryCurrencyPairs) { + let lookUp = {}; + if (!Array.isArray(countryCurrencyPairs)) { + throw new Error("Input must be an array of arrays"); + } + countryCurrencyPairs.forEach(pair => { + if (Array.isArray(pair) && pair.length === 2) { + lookUp[pair[0]] = pair[1]; + } else { + throw new Error("Each element of the input array must be an array of two elements"); + } + }); + + return lookUp; } module.exports = createLookup; diff --git a/Sprint-2/implement/lookup.test.js b/Sprint-2/implement/lookup.test.js index 547e06c5a..303794298 100644 --- a/Sprint-2/implement/lookup.test.js +++ b/Sprint-2/implement/lookup.test.js @@ -1,6 +1,49 @@ const createLookup = require("./lookup.js"); -test.todo("creates a country currency code lookup for multiple codes"); +describe("createLookup", () => { + test("creates a country currency code lookup for multiple codes", () => { + const countryCurrencyPairs = [['US', 'USD'], ['CA', 'CAD']]; + const expectedLookup = { + 'US': 'USD', + 'CA': 'CAD' + }; + expect(createLookup(countryCurrencyPairs)).toEqual(expectedLookup); + }); +}); + +test("creates a country currency code lookup for a single code", () => { + const countryCurrencyPairs = [['GB', 'GBP']]; + const expectedLookup = { + 'GB': 'GBP' + }; + expect(createLookup(countryCurrencyPairs)).toEqual(expectedLookup); +}); + +test("creates an empty lookup for an empty array", () => { + const countryCurrencyPairs = []; + const expectedLookup = {}; + expect(createLookup(countryCurrencyPairs)).toEqual(expectedLookup); +}); + +test("creates a country currency code lookup for multiple codes with different currencies", () => { + const countryCurrencyPairs = [['JP', 'JPY'], ['AU', 'AUD'], ['IN', 'INR']]; + const expectedLookup = { + 'JP': 'JPY', + 'AU': 'AUD', + 'IN': 'INR' + }; + expect(createLookup(countryCurrencyPairs)).toEqual(expectedLookup); +}); + +test("creates an array of array representing country code and currency code pairs", () => { + const countryCurrencyPairs = [['FR', 'EUR'], ['DE', 'EUR'], ['IT', 'EUR']]; + const expectedLookup = { + 'FR': 'EUR', + 'DE': 'EUR', + 'IT': 'EUR' + }; + expect(createLookup(countryCurrencyPairs)).toEqual(expectedLookup); +}); /* @@ -11,6 +54,8 @@ Acceptance Criteria: Given - An array of arrays representing country code and currency code pairs e.g. [['US', 'USD'], ['CA', 'CAD']] + tes + When - createLookup function is called with the country-currency array as an argument diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 45ec4e5f3..11840433c 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -1,16 +1,30 @@ function parseQueryString(queryString) { - const queryParams = {}; - if (queryString.length === 0) { - return queryParams; + const params = {}; +if (!queryString) { + return params; } - const keyValuePairs = queryString.split("&"); - for (const pair of keyValuePairs) { - const [key, value] = pair.split("="); - queryParams[key] = value; - } + const pairs = queryString.split("&"); + for (const pair of pairs) { + if (!pair) continue; // Ignore empty key-value pairs + + const [key, ...valueParts] = pair.split("="); + const value = valueParts.join("="); // Join back in case value contains '=' - return queryParams; + const decodedKey = decodeURIComponent(key.replace(/\+/g, " ")); + const decodedValue = decodeURIComponent(value.replace(/\+/g, " ")); + + if (params.hasOwnProperty(decodedKey)) { + if (Array.isArray(params[decodedKey])) { + params[decodedKey].push(decodedValue); + } else { + params[decodedKey] = [params[decodedKey], decodedValue]; + } + } else { + params[decodedKey] = decodedValue; + } + } + return params; } module.exports = parseQueryString; From ef554d37e92bb6bad92cc2b918a9eddc31b30c93 Mon Sep 17 00:00:00 2001 From: Zadri Abdule Date: Wed, 22 Jul 2026 21:00:12 +0100 Subject: [PATCH 13/16] implement tally and add tests --- Sprint-2/implement/tally.js | 15 ++++++++++++++- Sprint-2/implement/tally.test.js | 17 ++++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index f47321812..333265db4 100644 --- a/Sprint-2/implement/tally.js +++ b/Sprint-2/implement/tally.js @@ -1,3 +1,16 @@ -function tally() {} +function tally(items) { + if (!Array.isArray(items)) { + throw new Error('tally expects an array'); + } + + const counts = {}; + for (const item of items) { + // Use the item as the key; this will stringify non-string keys + const key = String(item); + counts[key] = (counts[key] || 0) + 1; + } + + return counts; +} module.exports = tally; diff --git a/Sprint-2/implement/tally.test.js b/Sprint-2/implement/tally.test.js index 2ceffa8dd..a5aceac2c 100644 --- a/Sprint-2/implement/tally.test.js +++ b/Sprint-2/implement/tally.test.js @@ -19,16 +19,31 @@ const tally = require("./tally.js"); // Given a function called tally // When passed an array of items // Then it should return an object containing the count for each unique item +test("tally returns an object containing the count for each unique item", () => { + const input = ["a"]; + const expected = { a: 1 }; + expect(tally(input)).toEqual(expected); +}); // Given an empty array // When passed to tally // Then it should return an empty object -test.todo("tally on an empty array returns an empty object"); +test("tally on an empty array returns an empty object", () => { + expect(tally([])).toEqual({}); +}); // Given an array with duplicate items // When passed to tally // Then it should return counts for each unique item +test("tally should return counts for each unique item", () => { + const input = ["a", "a", "b", "c"]; + const expected = { a: 2, b: 1, c: 1 }; + expect(tally(input)).toEqual(expected); +}); // Given an invalid input like a string // When passed to tally // Then it should throw an error +test("tally should throw on invalid input", () => { + expect(() => tally("not-an-array")).toThrow(); +}); From 7a3845439df898c2cd99c0e1f526070f5e991dfd Mon Sep 17 00:00:00 2001 From: Zadri Abdule Date: Sun, 26 Jul 2026 15:49:25 +0100 Subject: [PATCH 14/16] answered qustions from invert.js --- Sprint-2/interpret/invert.js | 26 +++++++++++++++++++++----- Sprint-2/interpret/invert.test.js | 7 +++++++ 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/Sprint-2/interpret/invert.js b/Sprint-2/interpret/invert.js index 99ace069f..40f67dd7f 100644 --- a/Sprint-2/interpret/invert.js +++ b/Sprint-2/interpret/invert.js @@ -5,28 +5,44 @@ // 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 = {}; + const inverted = {}; for (const [key, value] of Object.entries(obj)) { - // use computed property name so the value becomes the new key - invertedObj[String(value)] = key; + const v = String(value); + + if (v in inverted) { + // If value already exists + if (Array.isArray(inverted[v])) { + inverted[v].push(key); + } else { + inverted[v] = [inverted[v], key]; + } + } else { + inverted[v] = key; + } } - return invertedObj; + return inverted; } module.exports = invert; // a) What is the current return value when invert is called with { a : 1 } +// {"1":"a"} // b) What is the current return value when invert is called with { a: 1, b: 2 } +// {"1":"a", "2":"b"} // c) What is the target return value when invert is called with {a : 1, b: 2} +// the intended result is -> {"1":"a","2":"b"} (same as the as the current output) // c) What does Object.entries return? Why is it needed in this program? +// makes a list of pairs from the object: [[key1, value2], [key2, value2]] +// it's needed so that the code can look at the key and value together in each loop and build the swapped object. // d) Explain why the current return value is different from the target output +// In the examples shown, it matches the expectation. but: values are turned into strings when used as keys (so 1 becomes "1"). + // e) Fix the implementation of invert (and write tests to prove it's fixed!) diff --git a/Sprint-2/interpret/invert.test.js b/Sprint-2/interpret/invert.test.js index d779f1bec..f102f2010 100644 --- a/Sprint-2/interpret/invert.test.js +++ b/Sprint-2/interpret/invert.test.js @@ -11,3 +11,10 @@ test('inverts multiple pairs', () => { test('string values become keys', () => { expect(invert({ a: 'x', b: 'y' })).toEqual({ x: 'a', y: 'b' }); }); + +test('collects multiple keys that share the same value', () => { + const input = { a: 1, b: 1, c: 2 }; + const actual = invert(input); + expect(actual["1"]).toEqual(expect.arrayContaining(["a", "b"])); + expect(actual["2"]).toBe("c"); +}); \ No newline at end of file From 71029b2f8f0488b645387e423073b0e3e56a73a1 Mon Sep 17 00:00:00 2001 From: Zadri Abdule Date: Sun, 26 Jul 2026 16:49:36 +0100 Subject: [PATCH 15/16] add implemention for count-words.js --- Sprint-2/stretch/count-words.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Sprint-2/stretch/count-words.js b/Sprint-2/stretch/count-words.js index 8e85d19d7..f0b323123 100644 --- a/Sprint-2/stretch/count-words.js +++ b/Sprint-2/stretch/count-words.js @@ -26,3 +26,15 @@ 3. Order the results to find out which word is the most common in the input */ +function countWords(str) { + if (typeof str !== 'string') throw new Error('countWords expect a string'); + + const cleaned = str.toLowerCase().replace(/[^a-z0-9'\s]+/gi, " ").trim(); + if (cleaned === '') return 0; + + return cleaned.split(/\s+/).length; + + +} + +module.exports = countWords; \ No newline at end of file From 68b365c67a3e7e2b8031b5b7bca5bbd65246d541 Mon Sep 17 00:00:00 2001 From: Zadri Abdule Date: Sun, 26 Jul 2026 18:08:10 +0100 Subject: [PATCH 16/16] updated count-words.js and completed stretch tasks --- Sprint-2/stretch/count-words.js | 12 +++++++---- Sprint-2/stretch/mode.js | 27 +++++++++++++------------ Sprint-2/stretch/till.js | 35 +++++++++++++++++++++++++++++++-- 3 files changed, 55 insertions(+), 19 deletions(-) diff --git a/Sprint-2/stretch/count-words.js b/Sprint-2/stretch/count-words.js index f0b323123..4f9d97664 100644 --- a/Sprint-2/stretch/count-words.js +++ b/Sprint-2/stretch/count-words.js @@ -29,10 +29,14 @@ function countWords(str) { if (typeof str !== 'string') throw new Error('countWords expect a string'); - const cleaned = str.toLowerCase().replace(/[^a-z0-9'\s]+/gi, " ").trim(); - if (cleaned === '') return 0; - - return cleaned.split(/\s+/).length; + const cleaned = str.toLowerCase().replace(/[^a-z0-9'\s]+/gi, ' ').trim(); + if (cleaned === '') return {}; + + const counts = {}; + for (const w of cleaned.split(/\s+/)) { + counts[w] = (counts[w] || 0) + 1; + } + return counts; } diff --git a/Sprint-2/stretch/mode.js b/Sprint-2/stretch/mode.js index 3f7609d79..eb7275327 100644 --- a/Sprint-2/stretch/mode.js +++ b/Sprint-2/stretch/mode.js @@ -8,29 +8,30 @@ // refactor calculateMode by splitting up the code // into smaller functions using the stages above -function calculateMode(list) { - // track frequency of each value - let freqs = new Map(); - - for (let num of list) { - if (typeof num !== "number") { - continue; - } - - freqs.set(num, (freqs.get(num) || 0) + 1); +function buildFrequency(list) { + const freqs = new Map(); + for (const num of list) { + if (typeof num !== 'number') continue; + freqs.set(num, (freqs.get(num) ?? 0) + 1); } + return freqs; +} - // Find the value with the highest frequency +function findMode(freqs) { let maxFreq = 0; let mode; - for (let [num, freq] of freqs) { + for (const [num, freq] of freqs) { if (freq > maxFreq) { mode = num; maxFreq = freq; } } - return maxFreq === 0 ? NaN : mode; } +function calculateMode(list) { + const freqs = buildFrequency(list); + return findMode(freqs); +} + module.exports = calculateMode; diff --git a/Sprint-2/stretch/till.js b/Sprint-2/stretch/till.js index 6a08532e7..1a96c1367 100644 --- a/Sprint-2/stretch/till.js +++ b/Sprint-2/stretch/till.js @@ -8,10 +8,15 @@ function totalTill(till) { let total = 0; for (const [coin, quantity] of Object.entries(till)) { - total += coin * quantity; + // coin is like "1p" or "50p"; strip non-digits and parse to integer pence + const pence = parseInt(String(coin).replace(/\D+/g, ''), 10); + if (!Number.isFinite(pence)) continue; + if (typeof quantity !== 'number') continue; + + total += pence * quantity; } - return `£${total / 100}`; + return `£${(total / 100).toFixed(2)}`; } const till = { @@ -22,10 +27,36 @@ const till = { }; const totalAmount = totalTill(till); +module.exports = totalTill; + // a) What is the target output when totalTill is called with the till object +//Expected total in pounds: calculate coins in pence -> (1p * 10) + (5p * 6) + (50p * 4) + (20p * 10) +//1*10 = 10 +//5*6 = 30 +//50*4 = 200 +//20*10 = 200 +//Total pence = 440 -> £4.40 +//So target output string: "£4.40" // b) Why do we need to use Object.entries inside the for...of loop in this function? +// It gives [key, value] pairs so the loop can access both coin quantity directly without separate lookups. // c) What does coin * quantity evaluate to inside the for...of loop? +// coin * quantity evaluates evaluates to NaN because '1p' cannot be coerced to a number // d) Write a test for this function to check it works and then fix the implementation of totalTill +const totalTill = require('./till'); + +test('calculates total for sample till', () => { + const till = { '1p': 10, '5p': 6, '50p': 4, '20p': 10}; + expect(totalTill(till)).toBe('£4.40'); +}); + +test('empty till returns £0.00', () => { + expect(totalTill({})).toBe('£0.00'); +}); + +test('ignores invalid coin labels and quantities', () => { + const till = { '1p': 5, 'abc': 10, '10p': 'x', '20p': 3}; + expect(totalTill(till)).toBe('£0.65'); +}) \ No newline at end of file