From d6b4ea3598b7fe0caae5a347915840b04aea55b2 Mon Sep 17 00:00:00 2001 From: Imran Mohamed Date: Wed, 29 Oct 2025 10:53:07 +0000 Subject: [PATCH 01/14] 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 02/14] 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 03/14] 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 04/14] 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 05/14] 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 06/14] 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 07/14] 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 From df2c83ebb17f9c203e3baa261b8557d08585bd9b Mon Sep 17 00:00:00 2001 From: Imran Mohamed Date: Mon, 17 Nov 2025 16:25:47 +0000 Subject: [PATCH 08/14] fix logging of ingredients in recipe; ensure each ingredient is displayed on a new line --- Sprint-2/debug/recipe.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sprint-2/debug/recipe.js b/Sprint-2/debug/recipe.js index c349e82d3..cbcc647b3 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -14,4 +14,4 @@ const recipe = { console.log(`${recipe.title} serves ${recipe.serves} ingredients: -${recipe.ingredients}`); +${recipe.ingredients.join('\n')}`); From 1cef3734e56960826e94c5b136072e51de6740df Mon Sep 17 00:00:00 2001 From: Imran Mohamed Date: Mon, 17 Nov 2025 16:35:41 +0000 Subject: [PATCH 09/14] refactor createLookup function to use destructuring for country and currency; improve readability --- Sprint-2/implement/lookup.js | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/Sprint-2/implement/lookup.js b/Sprint-2/implement/lookup.js index f5e8beb02..52e5630cb 100644 --- a/Sprint-2/implement/lookup.js +++ b/Sprint-2/implement/lookup.js @@ -8,18 +8,24 @@ function createLookup(arr) { } 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; - } + + const [country, currency] = item; + if (currency === null || currency === undefined) { continue; } + + if (Array.isArray(currency) && typeof country === "string") { + result[country] + ? (result[country] = [result[country], currency]) + : (result[country] = currency); + } + if (country.length && currency.length && typeof country === 'string' && typeof currency === 'string') { - result[country] ? result[country] = [result[country], currency] - : result[country] = currency; + result[country] + ? (result[country] = [result[country], currency]) + : (result[country] = currency); } } } From 34a461a374db4571430cb5646c42b1ae77557ed5 Mon Sep 17 00:00:00 2001 From: Imran Mohamed Date: Mon, 17 Nov 2025 16:44:22 +0000 Subject: [PATCH 10/14] refactor parseQueryString to decode query string before processing; improve handling of encoded characters in tests --- Sprint-2/implement/querystring.js | 12 +++++++----- Sprint-2/implement/querystring.test.js | 4 ++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index aa601aa82..79a5ed73c 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -1,17 +1,19 @@ function parseQueryString(queryString) { const queryParams = {}; - if (queryString.length === 0) { + const decodedString = decodeURIComponent(queryString); + + if (decodedString.length === 0) { return queryParams; } - const keyValuePairs = queryString.split("&"); + + const keyValuePairs = decodedString.split("&"); for (const pair of keyValuePairs) { - if(pair.indexOf('=') !== pair.lastIndexOf('=')){ - const firstEqualIndex = pair.indexOf('='); + 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; diff --git a/Sprint-2/implement/querystring.test.js b/Sprint-2/implement/querystring.test.js index 4b34b3b09..8e12c2899 100644 --- a/Sprint-2/implement/querystring.test.js +++ b/Sprint-2/implement/querystring.test.js @@ -49,8 +49,8 @@ test("parses querystring with no equals sign", () => { test("parses querystring with encoded characters", () => { expect(parseQueryString("name=John%20Doe&city=New%20York")).toEqual({ - "name": "John%20Doe", - "city": "New%20York", + "name": "John Doe", + "city": "New York", }); }); From eabafb73048eb489d537753544ddd41bf7973435 Mon Sep 17 00:00:00 2001 From: Imran Mohamed Date: Tue, 18 Nov 2025 20:04:41 +0000 Subject: [PATCH 11/14] refactor tally function to handle null and undefined values as separate keys; update tests for clarity and edge cases --- Sprint-2/implement/tally.js | 24 ++++++++++++++++-------- Sprint-2/implement/tally.test.js | 10 ++++++++-- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index 10d62f655..fbf0ac1ae 100644 --- a/Sprint-2/implement/tally.js +++ b/Sprint-2/implement/tally.js @@ -1,12 +1,20 @@ 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; + if (!Array.isArray(arr) || arguments.length !== 1) { + throw new Error("Input must be an array"); + } + const result = Object.create(null); + for (const item of arr) { + + const key = + item === null ? "_null_" : item === undefined ? "_undefined_" : item; + + result[key] = (result[key] || 0) + 1; + + // result[item] = (result[item] || 0) + 1; + } + console.log(result); + + return result; } module.exports = tally; diff --git a/Sprint-2/implement/tally.test.js b/Sprint-2/implement/tally.test.js index af505f9f8..788ac87a7 100644 --- a/Sprint-2/implement/tally.test.js +++ b/Sprint-2/implement/tally.test.js @@ -49,11 +49,17 @@ test("should throw an error when no arguments are passed", () => { }); //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 null/undefined values with strings of null/undefined", () => { + expect(tally([null, undefined, null, 'a', undefined])).toEqual({ '_null_': 2, '_undefined_': 2, a: 1 }); + expect(tally([null, undefined, null, 'a', undefined, "null", "undefined"])).toEqual({ '_null_': 2, '_undefined_': 2, a: 1, "null": 1, "undefined": 1 }); + expect(tally([undefined, undefined, , ,null, null, null])).toEqual({ '_undefined_': 4, '_null_': 3 }); }); test("should handle array with special characters", () => { expect(tally(['@', '#', '@', '$', '%', '#', '@'])).toEqual({ '@': 3, '#': 2, '$': 1, '%': 1 }); }); + +test("should handle array with object method ", () => { + expect(tally(['toString', 'valueOf', 'toString', 'hasOwnProperty'])).toEqual({ toString: 2, valueOf: 1, hasOwnProperty: 1 }); +}); From 85e40acda374e757c66ae8b407506f128b2084bd Mon Sep 17 00:00:00 2001 From: Imran Mohamed Date: Sat, 22 Nov 2025 14:42:25 +0000 Subject: [PATCH 12/14] refactor createLookup function to simplify currency handling logic; improve readability and maintainability --- Sprint-2/implement/lookup.js | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/Sprint-2/implement/lookup.js b/Sprint-2/implement/lookup.js index 52e5630cb..f25de91a0 100644 --- a/Sprint-2/implement/lookup.js +++ b/Sprint-2/implement/lookup.js @@ -16,17 +16,11 @@ function createLookup(arr) { } - if (Array.isArray(currency) && typeof country === "string") { - result[country] - ? (result[country] = [result[country], currency]) - : (result[country] = currency); + if (((Array.isArray(currency) && currency.length)|| (typeof currency === 'string' && currency.length)) + && typeof country === "string" && country.length) { + result[country] = result[country] ? [result[country], currency] : currency; } - if (country.length && currency.length && typeof country === 'string' && typeof currency === 'string') { - result[country] - ? (result[country] = [result[country], currency]) - : (result[country] = currency); - } } } return result; From 359063e10053aad84893bd083131dc40c42dddbc Mon Sep 17 00:00:00 2001 From: Imran Mohamed Date: Tue, 25 Nov 2025 12:57:37 +0000 Subject: [PATCH 13/14] refactor parseQueryString to improve handling of empty query strings and key-value pairs; Implement decoding uri component at key value pair level; update tests for consistency with expected string outputs --- Sprint-2/implement/querystring.js | 26 ++++++++++++++++++-------- Sprint-2/implement/querystring.test.js | 6 +++--- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 79a5ed73c..9fa4ed0f5 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -1,23 +1,33 @@ function parseQueryString(queryString) { + const queryParams = {}; - const decodedString = decodeURIComponent(queryString); - if (decodedString.length === 0) { + if (!queryString.length) { return queryParams; } - const keyValuePairs = decodedString.split("&"); + const keyValuePairs = queryString.split("&").filter((pair) => pair.length); for (const pair of keyValuePairs) { + + let 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; + + key = pair.substring(0, firstEqualIndex); + value = pair.substring(firstEqualIndex + 1); + } else { - const [key, value] = pair.split("="); - queryParams[key] = value; + + [key, value] = pair.split("="); } + + key = decodeURIComponent(key); + value = decodeURIComponent(value); + + queryParams[key] = value; } return queryParams; diff --git a/Sprint-2/implement/querystring.test.js b/Sprint-2/implement/querystring.test.js index 8e12c2899..a15343db2 100644 --- a/Sprint-2/implement/querystring.test.js +++ b/Sprint-2/implement/querystring.test.js @@ -41,9 +41,9 @@ test("parses querystring with missing key", () => { test("parses querystring with no equals sign", () => { expect(parseQueryString("nameJohn&age30&cityNewYork")).toEqual({ - "nameJohn": undefined, - "age30": undefined, - "cityNewYork": undefined, + "nameJohn": "undefined", + "age30": "undefined", + "cityNewYork": "undefined", }); }); From 8d627ffd7b5b0c8c5f2666b7b597c1894d8b820f Mon Sep 17 00:00:00 2001 From: Imran Mohamed Date: Tue, 25 Nov 2025 13:38:37 +0000 Subject: [PATCH 14/14] refactor tally function to improve key generation for different data types; update tests for consistency with new key format --- Sprint-2/implement/tally.js | 8 +++-- Sprint-2/implement/tally.test.js | 51 ++++++++++++++++++++++++++++---- 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index fbf0ac1ae..5c3afe9a8 100644 --- a/Sprint-2/implement/tally.js +++ b/Sprint-2/implement/tally.js @@ -4,15 +4,17 @@ function tally(arr) { } const result = Object.create(null); for (const item of arr) { - const key = - item === null ? "_null_" : item === undefined ? "_undefined_" : item; + item === null + ? "_null_" + : item === undefined + ? "_undefined_" + : typeof item +' : ' +item; result[key] = (result[key] || 0) + 1; // result[item] = (result[item] || 0) + 1; } - console.log(result); return result; } diff --git a/Sprint-2/implement/tally.test.js b/Sprint-2/implement/tally.test.js index 788ac87a7..1fb70c02a 100644 --- a/Sprint-2/implement/tally.test.js +++ b/Sprint-2/implement/tally.test.js @@ -33,7 +33,7 @@ test("should return an empty object when given an empty array", () => { // 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 }); + expect(tally(['a', 'b', 'a', 'c', 'b', 'a'])).toEqual({ 'string : a': 3, 'string : b': 2, 'string : c': 1 }); }); // Given an invalid input like a string @@ -50,16 +50,57 @@ test("should throw an error when no arguments are passed", () => { //edge cases test("should handle array with null/undefined values with strings of null/undefined", () => { - expect(tally([null, undefined, null, 'a', undefined])).toEqual({ '_null_': 2, '_undefined_': 2, a: 1 }); - expect(tally([null, undefined, null, 'a', undefined, "null", "undefined"])).toEqual({ '_null_': 2, '_undefined_': 2, a: 1, "null": 1, "undefined": 1 }); + expect(tally([null, undefined, null, 'a', undefined])).toEqual({ '_null_': 2, '_undefined_': 2, 'string : a': 1 }); + expect( + tally([null, undefined, null, "a", undefined, "null", "undefined"]) + ).toEqual({ + _null_: 2, + _undefined_: 2, + "string : a": 1, + "string : null": 1, + "string : undefined": 1, + }); expect(tally([undefined, undefined, , ,null, null, null])).toEqual({ '_undefined_': 4, '_null_': 3 }); + expect(tally([null, undefined, "_null_", "_undefined_"])).toEqual({ + _null_: 1, + _undefined_: 1, + "string : _null_": 1, + "string : _undefined_": 1, + }); }); test("should handle array with special characters", () => { - expect(tally(['@', '#', '@', '$', '%', '#', '@'])).toEqual({ '@': 3, '#': 2, '$': 1, '%': 1 }); + expect(tally(["@", "#", "@", "$", "%", "#", "@"])).toEqual({ + "string : @": 3, + "string : #": 2, + "string : $": 1, + "string : %": 1, + }); }); test("should handle array with object method ", () => { - expect(tally(['toString', 'valueOf', 'toString', 'hasOwnProperty'])).toEqual({ toString: 2, valueOf: 1, hasOwnProperty: 1 }); + expect(tally(["toString", "valueOf", "toString", "hasOwnProperty"])).toEqual({ + "string : toString": 2, + "string : valueOf": 1, + "string : hasOwnProperty": 1, + }); }); + +test("should handle array with different data types", () => { + expect(tally([1, "1", true, false, 1, "1", true])).toEqual({ + "number : 1": 2, + "string : 1": 2, + "boolean : true": 2, + "boolean : false": 1, + }); +}); + +test("should handle array with negative numbers and zero", () => { + expect(tally([0, -1, -1, 0, 1, -1])).toEqual({ + "number : 0": 2, + "number : -1": 3, + "number : 1": 1, + }); +}); +