From d6b4ea3598b7fe0caae5a347915840b04aea55b2 Mon Sep 17 00:00:00 2001 From: Imran Mohamed Date: Wed, 29 Oct 2025 10:53:07 +0000 Subject: [PATCH 1/7] completed tasks set out in debug folder --- Sprint-2/debug/address.js | 4 +++- Sprint-2/debug/author.js | 6 ++++-- Sprint-2/debug/recipe.js | 4 +++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Sprint-2/debug/address.js b/Sprint-2/debug/address.js index 940a6af83..f6c3de686 100644 --- a/Sprint-2/debug/address.js +++ b/Sprint-2/debug/address.js @@ -1,5 +1,7 @@ // Predict and explain first... +//The code will not log the house number as it is using the wrong syntax to access the property. + // This code should log out the houseNumber from the address object // but it isn't working... // Fix anything that isn't working @@ -12,4 +14,4 @@ const address = { postcode: "XYZ 123", }; -console.log(`My house number is ${address[0]}`); +console.log(`My house number is ${address.houseNumber}`); diff --git a/Sprint-2/debug/author.js b/Sprint-2/debug/author.js index 8c2125977..3fa2a8627 100644 --- a/Sprint-2/debug/author.js +++ b/Sprint-2/debug/author.js @@ -1,5 +1,7 @@ // Predict and explain first... +// The code will not log the values of the object because a for of loop cannot be used on an object + // 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 +13,6 @@ const author = { alive: true, }; -for (const value of author) { - console.log(value); +for (const value in author) { + console.log(author[value]); } diff --git a/Sprint-2/debug/recipe.js b/Sprint-2/debug/recipe.js index 6cbdd22cd..c349e82d3 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -1,5 +1,7 @@ // Predict and explain first... +//The code is log the title and serves correctly, but will not log the ingredients properly because it is trying to log the entire recipe object and not the ingredients array + // This program should log out the title, how many it serves and the ingredients. // Each ingredient should be logged on a new line // How can you fix it? @@ -12,4 +14,4 @@ const recipe = { console.log(`${recipe.title} serves ${recipe.serves} ingredients: -${recipe}`); +${recipe.ingredients}`); From 3007268f06eb024d92e5ffadc87de06b02c4a483 Mon Sep 17 00:00:00 2001 From: Imran Mohamed Date: Wed, 29 Oct 2025 12:23:35 +0000 Subject: [PATCH 2/7] implement tests and code for contains function --- Sprint-2/implement/contains.js | 11 +++++- Sprint-2/implement/contains.test.js | 53 +++++++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/Sprint-2/implement/contains.js b/Sprint-2/implement/contains.js index cd779308a..c176caf16 100644 --- a/Sprint-2/implement/contains.js +++ b/Sprint-2/implement/contains.js @@ -1,3 +1,12 @@ -function contains() {} +function contains(obj, prop) { + if(arguments.length !== 2){ + throw new Error('Invalid input: function requires two arguments'); + } + if (obj === null || typeof obj !== 'object' || Array.isArray(obj)) { + throw new Error('Invalid input: first argument must be an object'); + } + 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..606c801f3 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -20,16 +20,65 @@ as the object doesn't contains a key of 'c' // Given an empty object // When passed to contains // Then it should return false -test.todo("contains on empty object returns false"); +test("should return false when passed an 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("should return true when the object contains the property", () => { + expect(contains({ a: 1, b: 2 }, "a")).toBe(true); +}); // Given an object with properties // When passed to contains with a non-existent property name -// Then it should return false +// Then it should return false +test("should return false when the object does not contain the property", () => { + 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 + +describe('error on invalid input', () => { + test.each([ + [null, 'a'], + [undefined, 'a'], + [42, 'a'], + ['string', 'a'], + [true, 'a'], + [[], 'a'], + ])('contains(%p, %p) should throw an error', (obj, prop) => { + expect(() => contains(obj, prop)).toThrow('Invalid input: first argument must be an object'); + }); + }); + +// Given an object with a property set to undefined +test("should return true when the property exists but is undefined", () => { + expect(contains({ a: undefined, b: 2 }, "a")).toBe(true); +}); + +// Given an object with a property set to null +test("should return true when the property exists but is null", () => { + expect(contains({ a: null, b: 2 }, "a")).toBe(true); +}); + +// Given an object with a property whose value is falsy +test("should return true when the property exists but is falsy", () => { + expect(contains({ a: 0, b: false, c: '', d: NaN }, "a")).toBe(true); + expect(contains({ a: 0, b: false, c: '', d: NaN }, "b")).toBe(true); + expect(contains({ a: 0, b: false, c: '', d: NaN }, "c")).toBe(true); + expect(contains({ a: 0, b: false, c: '', d: NaN }, "d")).toBe(true); +}); + +//incorrect arguments +test("should throw an error when called with incorrect number of arguments", () => { + expect(() => contains({ a: 1 })).toThrow('Invalid input: function requires two arguments'); + expect(() => contains()).toThrow('Invalid input: function requires two arguments'); + expect(() => contains({ a: 1 }, 'a', 'extra')).toThrow('Invalid input: function requires two arguments'); +}); + + + From b21936929b59447e3ef7bef5bb8bcd53a8b8851b Mon Sep 17 00:00:00 2001 From: Imran Mohamed Date: Fri, 31 Oct 2025 01:01:39 +0000 Subject: [PATCH 3/7] implement tests and code for lookup --- Sprint-2/implement/lookup.js | 30 ++++++- Sprint-2/implement/lookup.test.js | 129 +++++++++++++++++++++++++++++- 2 files changed, 152 insertions(+), 7 deletions(-) diff --git a/Sprint-2/implement/lookup.js b/Sprint-2/implement/lookup.js index a6746e07f..f5e8beb02 100644 --- a/Sprint-2/implement/lookup.js +++ b/Sprint-2/implement/lookup.js @@ -1,5 +1,29 @@ -function createLookup() { - // implementation here +function createLookup(arr) { + if(!arguments.length) { + throw new Error('No arguments provided'); + } + const result = {}; + if (!Array.isArray(arr)) { + return result; + } + for (const item of arr) { + if (Array.isArray(item) && item.length >= 2) { + const country = item[0]; + const currency = item[1]; + if (Array.isArray(currency)) { + result[country] = currency; + } + if (currency === null || currency === undefined) { + continue; + } + + if (country.length && currency.length && typeof country === 'string' && typeof currency === 'string') { + result[country] ? result[country] = [result[country], currency] + : result[country] = currency; + } + } + } + return result; } -module.exports = createLookup; +module.exports = createLookup diff --git a/Sprint-2/implement/lookup.test.js b/Sprint-2/implement/lookup.test.js index 547e06c5a..fe4990cb7 100644 --- a/Sprint-2/implement/lookup.test.js +++ b/Sprint-2/implement/lookup.test.js @@ -1,7 +1,4 @@ -const createLookup = require("./lookup.js"); - -test.todo("creates a country currency code lookup for multiple codes"); - +const createLookup = require("./lookup.js") /* Create a lookup object of key value pairs from an array of code pairs @@ -33,3 +30,127 @@ It should return: 'CA': 'CAD' } */ +//return an object +test('should return an object', () => { + expect(typeof createLookup([['US', 'USD'], ['CA', 'CAD']])).toBe('object'); +}) +//empty array +test('should handle empty array', () => { + const result = createLookup([]); + expect(result).toEqual({}); +}) +//single entry +test("should handle single entry", () => { + const result = createLookup([["FR", "EUR"]]); + expect(result).toEqual({ + FR: "EUR", + }); +}); +//multiple entries +test('should handle multiple entries', () => { + const result = createLookup([['US', 'USD'], ['CA', 'CAD'], ['GB', 'GBP']]); + expect(result).toEqual({ + 'US': 'USD', + 'CA': 'CAD', + 'GB': 'GBP' + }); +}) +//duplicate country codes +test('should handle duplicate country codes by creating an array for the values', () => { + const result = createLookup([['US', 'USD'], ['CA', 'CAD'], ['US', 'USN']]); + expect(result).toEqual({ + 'US': ['USD', 'USN'], + 'CA': 'CAD' + }); +}) +//special characters +test('should handle country codes with special characters', () => { + const result = createLookup([['UK-1', 'GBP'], ['EU@27', 'EUR']]); + expect(result).toEqual({ + 'UK-1': 'GBP', + 'EU@27': 'EUR' + }); +}) +//non-string country and currency codes +test('should omit non-string country and currency codes', () => { + const result = createLookup([[1, 100], [2, 200]]); + expect(result).toEqual({}); + const result2 = createLookup([['US', 100], [2, 'CAD']]); + expect(result2).toEqual({}) +}) +//nested arrays +test('should handle nested arrays as values', () => { + const result = createLookup([['US', ['USD', 'USN']], ['CA', ['CAD', 'CADN']]]); + expect(result).toEqual({ + 'US': ['USD', 'USN'], + 'CA': ['CAD', 'CADN'] + }); +}) +//null and undefined values +test('should omit null and undefined values', () => { + const result = createLookup([['US', null], ['CA', undefined]]); + expect(result).toEqual({}); +}) +//insufficient elements +test('should omit array with insufficient elements', () => { + const result = createLookup([['US', 'USD'], ['CA'], ['GB', 'GBP']]); + expect(result).toEqual({ + 'US': 'USD', + 'GB': 'GBP' + }); +}) +//extra elements +test('should handle array with extra elements', () => { + const result = createLookup([['US', 'USD', 'extra'], ['CA', 'CAD'], ['GB', 'GBP', 'extra2']]); + expect(result).toEqual({ + 'US': 'USD', + 'CA': 'CAD', + 'GB': 'GBP' + }); +}) +//whitespace in country and currency codes +test('should handle whitespace in country and currency codes', () => { + const result = createLookup([[' US ', ' USD '], [' CA ', ' CAD ']]); + expect(result).toEqual({ + ' US ': ' USD ', + ' CA ': ' CAD ' + }); +}) +//empty strings +test('should omit empty strings as country and currency codes', () => { + const result = createLookup([['', ''], ['CA', 'CAD']]); + expect(result).toEqual({ + 'CA': 'CAD' + }); +}) +//invalid array elements +test('should handle array with non-array and insufficient/extra elements', () => { + const result = createLookup([['US', 'USD'], 'invalid', ['CA'], ['GB', 'GBP']]); + expect(result).toEqual({ + 'US': 'USD', + 'GB': 'GBP' + }); + const result2 = createLookup([ + ["US", "USD", "extra"], + "invalid", + ["CA", "CAD"], + ["GB", "GBP", "extra2"], + ]); + expect(result2).toEqual({ + US: "USD", + CA: "CAD", + GB: "GBP", + }); +}) +//incorrect argument types +test('should return empty object for non-array arguments', () => { + expect(createLookup(null)).toEqual({}); + expect(createLookup(undefined)).toEqual({}); + expect(createLookup(123)).toEqual({}); + expect(createLookup('string')).toEqual({}); + expect(createLookup({})).toEqual({}); +}) +//no arguments +test('should throw error when no arguments are provided', () => { + expect(() => createLookup()).toThrow('No arguments provided'); +}) \ No newline at end of file From 5a3a24719fee2c66227878cd45bdbff435a5e621 Mon Sep 17 00:00:00 2001 From: Imran Mohamed Date: Sat, 1 Nov 2025 08:53:59 +0000 Subject: [PATCH 4/7] added querystring tests --- Sprint-2/implement/querystring.test.js | 69 ++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/Sprint-2/implement/querystring.test.js b/Sprint-2/implement/querystring.test.js index 3e218b789..4b34b3b09 100644 --- a/Sprint-2/implement/querystring.test.js +++ b/Sprint-2/implement/querystring.test.js @@ -10,3 +10,72 @@ test("parses querystring values containing =", () => { "equation": "x=y+1", }); }); +//other edge cases to consider: +test("parses empty querystring", () => { + expect(parseQueryString("")).toEqual({}); +}); + +test("parses querystring with multiple key-value pairs", () => { + expect(parseQueryString("name=John&age=30&city=NewYork")).toEqual({ + "name": "John", + "age": "30", + "city": "NewYork", + }); +}); + +test("parses querystring with missing value", () => { + expect(parseQueryString("name=John&age=&city=NewYork")).toEqual({ + "name": "John", + "age": "", + "city": "NewYork", + }); +}); + +test("parses querystring with missing key", () => { + expect(parseQueryString("=John&age=30&city=NewYork")).toEqual({ + "": "John", + "age": "30", + "city": "NewYork", + }); +}); + +test("parses querystring with no equals sign", () => { + expect(parseQueryString("nameJohn&age30&cityNewYork")).toEqual({ + "nameJohn": undefined, + "age30": undefined, + "cityNewYork": undefined, + }); +}); + +test("parses querystring with encoded characters", () => { + expect(parseQueryString("name=John%20Doe&city=New%20York")).toEqual({ + "name": "John%20Doe", + "city": "New%20York", + }); +}); + +test("parses querystring with repeated keys", () => { + expect(parseQueryString("name=John&name=Jane&age=30")).toEqual({ + "name": "Jane", + "age": "30", + }); +}); + +test("parses querystring with special characters in keys and values", () => { + expect(parseQueryString("na!me=Jo@hn&ag#e=3$0")).toEqual({ + "na!me": "Jo@hn", + "ag#e": "3$0", + }); +}); +test("parses querystring with leading and trailing ampersands", () => { + expect(parseQueryString("&name=John&age=30&")).toEqual({ + "name": "John", + "age": "30", + }); +}); + +test("parses querystring with multiple equals signs in value", () => { + expect(parseQueryString("data=a=b=c=d")).toEqual({ + "data": "a=b=c=d", + }); +}); \ No newline at end of file From 2556038fcb8a15406d5654b332f1539076cc76d6 Mon Sep 17 00:00:00 2001 From: Imran Mohamed Date: Sat, 1 Nov 2025 14:21:44 +0000 Subject: [PATCH 5/7] implement tally function and corresponding tests for various cases --- Sprint-2/implement/tally.js | 11 ++++++++++- Sprint-2/implement/tally.test.js | 29 +++++++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index f47321812..10d62f655 100644 --- a/Sprint-2/implement/tally.js +++ b/Sprint-2/implement/tally.js @@ -1,3 +1,12 @@ -function tally() {} +function tally(arr) { + if (!Array.isArray(arr) || arguments.length !== 1) { + throw new Error("Input must be an array"); + } + const result = {}; + for (const item of arr) { + result[item] = (result[item] || 0) + 1; + } + return result; +} module.exports = tally; diff --git a/Sprint-2/implement/tally.test.js b/Sprint-2/implement/tally.test.js index 2ceffa8dd..af505f9f8 100644 --- a/Sprint-2/implement/tally.test.js +++ b/Sprint-2/implement/tally.test.js @@ -19,16 +19,41 @@ 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('should return an object', () => { + expect(typeof tally([])).toBe('object'); +}) // 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("should return an empty object when given an empty array", () => { + expect(tally([])).toEqual({}); +}); // Given an array with duplicate items // When passed to tally // Then it should return counts for each unique item +test("should return correct counts for each unique item", () => { + expect(tally(['a', 'b', 'a', 'c', 'b', 'a'])).toEqual({ a: 3, b: 2, c: 1 }); +}); // Given an invalid input like a string // When passed to tally // Then it should throw an error +test("should throw an error when given invalid input", () => { + expect(() => tally("invalid input")).toThrow("Input must be an array"); +}); + +//no args passed +test("should throw an error when no arguments are passed", () => { + expect(() => tally()).toThrow("Input must be an array"); +}); + +//edge cases +test("should handle array with null and undefined values", () => { + expect(tally([null, undefined, null, 'a', undefined])).toEqual({ null: 2, undefined: 2, a: 1 }); +}); + +test("should handle array with special characters", () => { + expect(tally(['@', '#', '@', '$', '%', '#', '@'])).toEqual({ '@': 3, '#': 2, '$': 1, '%': 1 }); +}); + From da1b806203d19ff4b6cebded27ffaa06b468f2e3 Mon Sep 17 00:00:00 2001 From: Imran Mohamed Date: Sat, 1 Nov 2025 14:37:37 +0000 Subject: [PATCH 6/7] refactor parseQueryString to handle multiple equal signs in query parameters --- Sprint-2/implement/querystring.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 45ec4e5f3..aa601aa82 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -6,8 +6,16 @@ function parseQueryString(queryString) { const keyValuePairs = queryString.split("&"); for (const pair of keyValuePairs) { - const [key, value] = pair.split("="); - queryParams[key] = value; + if(pair.indexOf('=') !== pair.lastIndexOf('=')){ + const firstEqualIndex = pair.indexOf('='); + const key = pair.substring(0, firstEqualIndex); + const value = pair.substring(firstEqualIndex + 1); + queryParams[key] = value; + continue; + } else { + const [key, value] = pair.split("="); + queryParams[key] = value; + } } return queryParams; From 79435416b169a979216cbb0e6d291a6cd863ad0f Mon Sep 17 00:00:00 2001 From: Imran Mohamed Date: Sun, 2 Nov 2025 01:36:55 +0000 Subject: [PATCH 7/7] correct invert implementation to swap keys and values; add tests for various cases --- Sprint-2/interpret/invert.js | 13 ++++++- Sprint-2/interpret/invert.test.js | 64 +++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 Sprint-2/interpret/invert.test.js diff --git a/Sprint-2/interpret/invert.js b/Sprint-2/interpret/invert.js index bb353fb1f..7f75301a9 100644 --- a/Sprint-2/interpret/invert.js +++ b/Sprint-2/interpret/invert.js @@ -7,23 +7,34 @@ // E.g. invert({x : 10, y : 20}), target output: {"10": "x", "20": "y"} function invert(obj) { + if (obj === null || typeof obj !== 'object' || Array.isArray(obj)) { + throw new Error('Input must be an object'); + } + const invertedObj = {}; for (const [key, value] of Object.entries(obj)) { - invertedObj.key = value; + invertedObj[value] = key; } return invertedObj; } // 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} +// {"1": "a", "2": "b"} // c) What does Object.entries return? Why is it needed in this program? +// It returns an array of key value pairs. It is needed to iterate through the object. // d) Explain why the current return value is different from the target output +// The current implementation is setting the 'key' as the property name instead of using the value of the variable key. +// Additionally there is no inversion happening, the value is being assigned to the key property instead of swapping them. // e) Fix the implementation of invert (and write tests to prove it's fixed!) +module.exports = invert; \ No newline at end of file diff --git a/Sprint-2/interpret/invert.test.js b/Sprint-2/interpret/invert.test.js new file mode 100644 index 000000000..e1d529d31 --- /dev/null +++ b/Sprint-2/interpret/invert.test.js @@ -0,0 +1,64 @@ +const invert = require('./invert'); + + test('should return an object', () => { + const input = { a: 1 }; + expect(typeof invert(input)).toBe('object'); + }) + + test('should invert an object with a single key-value pair', () => { + const input = { a: 1 }; + const expectedOutput = { '1': 'a' }; + expect(invert(input)).toEqual(expectedOutput); + }); + + test('should invert an object with multiple key-value pairs', () => { + const input = { a: 1, b: 2 }; + const expectedOutput = { '1': 'a', '2': 'b' }; + expect(invert(input)).toEqual(expectedOutput); + }); + +// Additional test cases + test('should handle empty object', () => { + const input = {}; + const expectedOutput = {}; + expect(invert(input)).toEqual(expectedOutput); + }); + + test('should handle non-string values', () => { + const input = { x: 10, y: 20 }; + const expectedOutput = { '10': 'x', '20': 'y' }; + expect(invert(input)).toEqual(expectedOutput); + }); + + test('should handle duplicate values by overwriting', () => { + const input = { a: 1, b: 1 }; + const expectedOutput = { '1': 'b' }; // 'b' overwrites 'a' + expect(invert(input)).toEqual(expectedOutput); + }); + //invalid input + test('should throw an error for non-object input', () => { + expect(() => invert(null)).toThrowError('Input must be an object'); + expect(() => invert(42)).toThrowError('Input must be an object'); + expect(() => invert("string")).toThrowError('Input must be an object'); + expect(() => invert([1, 2, 3])).toThrowError('Input must be an object'); + }); + +// test('should handle object with non-string keys and values', () => { +// const input = { 1: 'one', 2: 'two' }; +// const expectedOutput = { 'one': '1', 'two': '2' }; +// expect(invert(input)).toEqual(expectedOutput); +// }); + +// test('should handle object with boolean values', () => { +// const input = { a: true, b: false }; +// const expectedOutput = { 'true': 'a', 'false': 'b' }; +// expect(invert(input)).toEqual(expectedOutput); +// }); + +// test('should handle object with mixed value types', () => { +// const input = { a: 1, b: 'two', c: true }; +// const expectedOutput = { '1': 'a', 'two': 'b', 'true': 'c' }; +// expect(invert(input)).toEqual(expectedOutput); +// }); + +// test('should handle object with nested objects as values', () => { \ No newline at end of file