diff --git a/Sprint-2/debug/address.js b/Sprint-2/debug/address.js index 940a6af83..7ddb854b9 100644 --- a/Sprint-2/debug/address.js +++ b/Sprint-2/debug/address.js @@ -1,5 +1,9 @@ // Predict and explain first... +// I guess the bug could be related to how to access property in an object. +// address[0] may not be able to access houseNumber as a key +// if we change address[0] to address['houseNumber'] or address.houseNumber, it may resolve the problem. + // This code should log out the houseNumber from the address object // but it isn't working... // Fix anything that isn't working @@ -12,4 +16,7 @@ const address = { postcode: "XYZ 123", }; -console.log(`My house number is ${address[0]}`); +console.log(`My house number is ${address.houseNumber}`); + +// After trying both address["houseNumber"] and address.houseNumber, +// console.log can successfully print out the statement with right number accordingly. diff --git a/Sprint-2/debug/author.js b/Sprint-2/debug/author.js index 8c2125977..06360ed08 100644 --- a/Sprint-2/debug/author.js +++ b/Sprint-2/debug/author.js @@ -1,5 +1,7 @@ // Predict and explain first... +// I guess the problem could be induced by console.log(value), as each value is a key: value pair, but we only need the values. + // 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,9 @@ const author = { alive: true, }; -for (const value of author) { - console.log(value); -} +console.log(Object.values(author)); + +// After running the code, I got TypeError: author is not iterable +// so, the actual problem is an object cannot be iterated in for loop. +// To print out all the values in author, I attempted Object.values(author) +// which return all the values in an array [ 'Zadie', 'Smith', 'writer', 40, true ] diff --git a/Sprint-2/debug/recipe.js b/Sprint-2/debug/recipe.js index 6cbdd22cd..bc4a57e40 100644 --- a/Sprint-2/debug/recipe.js +++ b/Sprint-2/debug/recipe.js @@ -1,5 +1,9 @@ // Predict and explain first... +// For the first glance, I guess I have to do a looping in recipe.ingredients +// Before any changes, ingredients: ${recipe} would be print out the whole object. +// for print out each ingredient on a new line, I may have to use \n + // 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 +16,7 @@ const recipe = { console.log(`${recipe.title} serves ${recipe.serves} ingredients: -${recipe}`); +${recipe.ingredients.join("\n")}`); + +// After the first attempt, I realized that I can't use a for loop inside ${} +// so, I tried map function to get each ingredient and added \n between them. diff --git a/Sprint-2/implement/contains.js b/Sprint-2/implement/contains.js index cd779308a..5784f3afb 100644 --- a/Sprint-2/implement/contains.js +++ b/Sprint-2/implement/contains.js @@ -1,3 +1,8 @@ -function contains() {} +function contains(obj, targetProperty) { + if (typeof obj !== "object" || Array.isArray(obj) || obj === null) { + throw new Error("Input should be an object"); + } + return Object.keys(obj).includes(targetProperty); +} module.exports = contains; diff --git a/Sprint-2/implement/contains.test.js b/Sprint-2/implement/contains.test.js index 326bdb1f2..bb604827e 100644 --- a/Sprint-2/implement/contains.test.js +++ b/Sprint-2/implement/contains.test.js @@ -20,16 +20,35 @@ 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("contains on empty object returns false", () => { + const emptyObject = {}; + expect(contains(emptyObject, "a")).toBe(false); +}); // Given an object with properties // When passed to contains with an existing property name // Then it should return true +test("contains on an object with properties, passed an existing property name, returns true", () => { + const obj = { a: 1, b: 2 }; + expect(contains(obj, "a")).toBe(true); +}); // Given an object with properties // When passed to contains with a non-existent property name // Then it should return false +test("contains on an object properties, passed a non-existing property name, returns false", () => { + const obj = { a: 1, b: 2 }; + expect(contains(obj, "c")).toBe(false); +}); // Given invalid parameters like an array // When passed to contains // Then it should return false or throw an error +test("Given invalid parameters returns an error", () => { + const errorMessage = "Input should be an object"; + expect(() => contains([{ a: 1 }, { b: 2 }], "a")).toThrow(errorMessage); + expect(() => contains(null, "a")).toThrow(errorMessage); + expect(() => contains(undefined, "a")).toThrow(errorMessage); + expect(() => contains(true, "a")).toThrow(errorMessage); + expect(() => contains(false, "a")).toThrow(errorMessage); +}); diff --git a/Sprint-2/implement/lookup.js b/Sprint-2/implement/lookup.js index a6746e07f..b55c808e5 100644 --- a/Sprint-2/implement/lookup.js +++ b/Sprint-2/implement/lookup.js @@ -1,5 +1,16 @@ -function createLookup() { - // implementation here +function createLookup(arr) { + if (!Array.isArray(arr)) { + throw new Error("Input should be an array of arrays of code pairs"); + } + + const validArray = arr.every( + (element) => Array.isArray(element) && element.length === 2 + ); + if (!validArray) { + throw new Error("Input should be an array of arrays of code pairs"); + } + + return Object.fromEntries(arr); } module.exports = createLookup; diff --git a/Sprint-2/implement/lookup.test.js b/Sprint-2/implement/lookup.test.js index 547e06c5a..c3b505879 100644 --- a/Sprint-2/implement/lookup.test.js +++ b/Sprint-2/implement/lookup.test.js @@ -1,6 +1,75 @@ const createLookup = require("./lookup.js"); -test.todo("creates a country currency code lookup for multiple codes"); +const errorMessage = "Input should be an array of arrays of code pairs"; + +test("Given an empty array, returns an empty object", () => { + expect(createLookup([])).toStrictEqual({}); +}); + +test("Given invalid parameter, return error message", () => { + expect(() => createLookup("Invalid input")).toThrow(errorMessage); + expect(() => createLookup({ a: "b" })).toThrow(errorMessage); + expect(() => createLookup(123)).toThrow(errorMessage); + expect(() => createLookup(null)).toThrow(errorMessage); + expect(() => createLookup(undefined)).toThrow(errorMessage); + expect(() => createLookup(true)).toThrow(errorMessage); +}); + +test("creates a country currency code lookup for multiple codes", () => { + const validInput = [ + ["US", "USD"], + ["CA", "CAD"], + ["JP", "JPY"], + ]; + const target = { + US: "USD", + CA: "CAD", + JP: "JPY", + }; + expect(createLookup(validInput)).toStrictEqual(target); +}); + +test("Given an array with empty arrays, returns error message", () => { + const emptyArrays = [[], []]; + expect(() => createLookup(emptyArrays)).toThrow(errorMessage); + const emptyArrays2 = [[], [], ["JP", "JPY"]]; + expect(() => createLookup(emptyArrays2)).toThrow(errorMessage); +}); + +test("Given an array with objects, return error message", () => { + const objectsArray = [{ a: "b" }, { c: "d" }]; + expect(() => createLookup(objectsArray)).toThrow(errorMessage); +}); + +test("Given an array mixed with valid arrays and invalid input, returns error message", () => { + const mixInput = [["US", "USD"], { CA: "CAD" }, ["JP", "JPY"]]; + expect(() => createLookup(mixInput).toThrow(errorMessage)); + const mixInput2 = [["US", "USD"], undefined, ["JP", "JPY"]]; + expect(() => createLookup(mixInput2).toThrow(errorMessage)); + const mixInput3 = [["US", "USD"], null, ["JP", "JPY"]]; + expect(() => createLookup(mixInput3).toThrow(errorMessage)); +}); + +test("Given an array with arrays but some are displaced, return error message", () => { + const missFormatInput = [["US", "USD"], ["CA"], ["JP", "JPY", "CAD"]]; + expect(() => createLookup(missFormatInput)).toThrow(errorMessage); +}); + +test("Given an array with array of non-strings, return accordingly", () => { + const nonStringInput = [ + [undefined, true], + ["CA", "CAD"], + ["JP", null], + [123, 456], + ]; + const target = { + undefined: true, + CA: "CAD", + JP: null, + 123: 456, + }; + expect(createLookup(nonStringInput)).toStrictEqual(target); +}); /* diff --git a/Sprint-2/implement/querystring.js b/Sprint-2/implement/querystring.js index 45ec4e5f3..5159e1493 100644 --- a/Sprint-2/implement/querystring.js +++ b/Sprint-2/implement/querystring.js @@ -3,10 +3,28 @@ function parseQueryString(queryString) { if (queryString.length === 0) { return queryParams; } + + if (queryString[0] === "?") { + queryString = queryString.slice(1); + } + const keyValuePairs = queryString.split("&"); for (const pair of keyValuePairs) { - const [key, value] = pair.split("="); + if (!pair) continue; + + const indexToSlice = pair.indexOf("="); + + let key; + let value; + + if (indexToSlice === -1) { + key = pair; + value = ""; + } else { + key = decodeURIComponent(pair.slice(0, indexToSlice)); + value = decodeURIComponent(pair.slice(indexToSlice + 1)); + } queryParams[key] = value; } diff --git a/Sprint-2/implement/querystring.test.js b/Sprint-2/implement/querystring.test.js index 3e218b789..188eac8a6 100644 --- a/Sprint-2/implement/querystring.test.js +++ b/Sprint-2/implement/querystring.test.js @@ -3,10 +3,48 @@ // Below is one test case for an edge case the implementation doesn't handle well. // Fix the implementation for this test, and try to think of as many other edge cases as possible - write tests and fix those too. -const parseQueryString = require("./querystring.js") +const parseQueryString = require("./querystring.js"); test("parses querystring values containing =", () => { expect(parseQueryString("equation=x=y+1")).toEqual({ - "equation": "x=y+1", + equation: "x=y+1", + }); +}); + +test("Single Key with No Value", () => { + expect(parseQueryString("key=")).toEqual({ key: "" }); +}); + +test("Key Without = Sign", () => { + expect(parseQueryString("aloneKey")).toEqual({ aloneKey: "" }); +}); + +test("Multiple Parameters", () => { + expect(parseQueryString("name=Brian&age=35&country=UK")).toEqual({ + name: "Brian", + age: "35", + country: "UK", + }); +}); + +test("Duplicate Keys", () => { + expect(parseQueryString("tag=js&tag=node&tag=react")).toEqual({ + tag: "react", + }); +}); + +test("Values Containing Special Characters", () => { + expect(parseQueryString("query=a%20b%26c%3Dd")).toEqual({ query: "a b&c=d" }); + expect(parseQueryString("a=1&b=2&")).toEqual({ a: "1", b: "2" }); +}); + +test("Starting with ?", () => { + expect(parseQueryString("?foo=bar")).toEqual({ foo: "bar" }); +}); + +test("Mixed encoded and plain parts", () => { + expect(parseQueryString("message=Hello%20World%21&debug=true")).toEqual({ + message: "Hello World!", + debug: "true", }); }); diff --git a/Sprint-2/implement/tally.js b/Sprint-2/implement/tally.js index f47321812..1517c3555 100644 --- a/Sprint-2/implement/tally.js +++ b/Sprint-2/implement/tally.js @@ -1,3 +1,22 @@ -function tally() {} +function tally(arr) { + if (!Array.isArray(arr)) { + throw new Error("Input should be an array"); + } + if (arr.length === 0) { + return {}; + } + + arr = arr.flat(); + + let result = {}; + for (element of arr) { + const key = + typeof element === "object" ? JSON.stringify(element) : String(element); + + result[key] = (result[key] || 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..c800e02fe 100644 --- a/Sprint-2/implement/tally.test.js +++ b/Sprint-2/implement/tally.test.js @@ -23,12 +23,71 @@ const tally = require("./tally.js"); // 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("Given an array with duplicate item, it should return counts for each unique item", () => { + expect(tally(["a", "a", "a"])).toEqual({ a: 3 }); + expect(tally(["a", "a", "b", "c"])).toEqual({ a: 2, b: 1, c: 1 }); + expect(tally(["a", "a", "b", "c", "a", "a", "b", "c"])).toEqual({ + a: 4, + b: 2, + c: 2, + }); +}); // Given an invalid input like a string // When passed to tally // Then it should throw an error +const errorMessage = "Input should be an array"; +test("Given a non array input, it should return an error message", () => { + expect(() => tally({ a: "b" })).toThrow(errorMessage); + expect(() => tally("a")).toThrow(errorMessage); + expect(() => tally(123)).toThrow(errorMessage); + expect(() => tally(null)).toThrow(errorMessage); + expect(() => tally(undefined)).toThrow(errorMessage); + expect(() => tally(true)).toThrow(errorMessage); +}); + +test("Given an array with numbers, it should return counts for each number", () => { + expect(tally([1, 1, 1])).toEqual({ 1: 3 }); + expect(tally([12, 12, 12])).toEqual({ 12: 3 }); + expect(tally([1, 12, 123])).toEqual({ 1: 1, 12: 1, 123: 1 }); + expect(tally([1, 12, 123, 1, 12, 123])).toEqual({ 1: 2, 12: 2, 123: 2 }); +}); + +test("Given an array with mixed items, it should return counts for each item", () => { + expect( + tally(["a", 1, true, null, undefined, "a", 1, true, null, undefined]) + ).toEqual({ + a: 2, + 1: 2, + true: 2, + null: 2, + undefined: 2, + }); +}); + +test("Given an array with arrays, it should return counts for each item", () => { + expect(tally([1, [1, 1]])).toEqual({ 1: 3 }); + expect(tally(["a", ["a", "a"]])).toEqual({ a: 3 }); +}); + +test("Given an array with objects, it should return counts for each object", () => { + expect( + tally([ + { a: 1, b: 2, c: 3 }, + { a: 1, b: 2, c: 3 }, + ]) + ).toEqual({ '{"a":1,"b":2,"c":3}': 2 }); +}); + +test("Given an array mixed with arrays and objects, it should return counts for each item", () => { + expect( + tally([{ a: 1, b: 2, c: 3 }, "a", { a: 1, b: 2, c: 3 }, ["a", "a"]]) + ).toEqual({ '{"a":1,"b":2,"c":3}': 2, a: 3 }); +}); diff --git a/Sprint-2/interpret/invert.js b/Sprint-2/interpret/invert.js index bb353fb1f..8b0cbec13 100644 --- a/Sprint-2/interpret/invert.js +++ b/Sprint-2/interpret/invert.js @@ -7,23 +7,50 @@ // E.g. invert({x : 10, y : 20}), target output: {"10": "x", "20": "y"} function invert(obj) { + if (typeof obj !== "object" || obj === null || Array.isArray(obj)) { + throw new Error("Input should be an object"); + } + const invertedObj = {}; - for (const [key, value] of Object.entries(obj)) { - invertedObj.key = value; + for (let [key, value] of Object.entries(obj)) { + if (typeof key === "object") { + key = JSON.stringify(key); + } + if (typeof value === "object") { + value = JSON.stringify(value); + } + invertedObj[value] = key; } return invertedObj; } +module.exports = invert; + // a) What is the current return value when invert is called with { a : 1 } +// When invert is called with { a: 1 }, it returns { key: 1 } +// I found the way of assigning key and value is currently not right. // b) What is the current return value when invert is called with { a: 1, b: 2 } +// The current return is { key: 2 } +// The value of key changed from 1 to 2, when it is reassigned again in the for loop // c) What is the target return value when invert is called with {a : 1, b: 2} +// The target return value was supposed to be { '1': 'a', '2': 'b' } +// as the original key and value is swapped // c) What does Object.entries return? Why is it needed in this program? +// Object.entries return an array of arrays of each pair of key and value like [['a', '1'], ['b', '2']] +// if Object.entries({ a: 1, b: 2 }) is called +// We need it because we need to take each pair of key and value [key, value] in array like ['a', '1'] +// and swap them in the next step of the for loop // d) Explain why the current return value is different from the target output +// The current way of assigning key and value invertedObj.key = value; +// can't actually access the referencing value of key, which is 'a' +// In fact, it created a new key named 'key', rather than 'a' +// Besides, as we want to swap the key: value to value: key +// we should refactor the assigning to invertedObj[value] = key // 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 new file mode 100644 index 000000000..b1a1da923 --- /dev/null +++ b/Sprint-2/interpret/invert.test.js @@ -0,0 +1,39 @@ +const invert = require("./invert"); + +describe("testing invert function", () => { + const errorMessage = "Input should be an object"; + it("should throw error message when input is not an object", () => { + expect(() => invert("abc")).toThrow(errorMessage); + expect(() => invert(123)).toThrow(errorMessage); + expect(() => invert(null)).toThrow(errorMessage); + expect(() => invert(undefined)).toThrow(errorMessage); + expect(() => invert(true)).toThrow(errorMessage); + expect(() => invert(["a", "1"])).toThrow(errorMessage); + }); + + it("should return a swapped object when an object is passed in the function", () => { + expect(invert({ a: "1" })).toStrictEqual({ 1: "a" }); + expect(invert({ a: 1, b: 2, c: 3 })).toStrictEqual({ + 1: "a", + 2: "b", + 3: "c", + }); + expect(invert({ a: null, b: undefined, c: true })).toStrictEqual({ + null: "a", + undefined: "b", + true: "c", + }); + expect(invert({ a: [1], b: [2] })).toStrictEqual({ + "[1]": "a", + "[2]": "b", + }); + expect(invert({ a: { b: 1 }, c: { d: 2 } })).toStrictEqual({ + '{"b":1}': "a", + '{"d":2}': "c", + }); + expect(invert({ "{a: b}": 1, "{c: d}": 2 })).toStrictEqual({ + 1: "{a: b}", + 2: "{c: d}", + }); + }); +}); diff --git a/Sprint-2/stretch/count-word.test.js b/Sprint-2/stretch/count-word.test.js new file mode 100644 index 000000000..30434408b --- /dev/null +++ b/Sprint-2/stretch/count-word.test.js @@ -0,0 +1,115 @@ +const countWords = require("./count-words"); + +test("Given an empty string, it should return an empty object", () => { + expect(countWords("")).toStrictEqual({}); +}); + +test("Given an non string input, it should throw an error message", () => { + const errorMessage = "Input should be a string"; + expect(() => countWords(["non string"])).toThrow(errorMessage); + expect(() => countWords({ a: 1 })).toThrow(errorMessage); + expect(() => countWords(123)).toThrow(errorMessage); + expect(() => countWords(null)).toThrow(errorMessage); + expect(() => countWords(undefined)).toThrow(errorMessage); + expect(() => countWords(true)).toThrow(errorMessage); +}); + +test("Given a valid string, it should return an object with proper count on each word", () => { + expect(countWords("you and me and you")).toStrictEqual({ + you: 2, + and: 2, + me: 1, + }); + expect( + countWords( + "The government has announced plans to train and recruit more workers for the UK's clean energy sector" + ) + ).toStrictEqual({ + the: 2, + government: 1, + has: 1, + announced: 1, + plans: 1, + to: 1, + train: 1, + and: 1, + recruit: 1, + more: 1, + workers: 1, + for: 1, + "uk's": 1, + clean: 1, + energy: 1, + sector: 1, + }); +}); + +test("Given a valid string with punctuation, it should count each word without punctuation", () => { + expect( + JSON.stringify( + countWords( + "The government has announced plans to train and recruit more workers for the UK's clean energy sector, promising to create 400,000 extra jobs by 2030." + ) + ) + ).toStrictEqual( + JSON.stringify({ + the: 2, + to: 2, + government: 1, + has: 1, + announced: 1, + plans: 1, + train: 1, + and: 1, + recruit: 1, + more: 1, + workers: 1, + for: 1, + "uk's": 1, + clean: 1, + energy: 1, + sector: 1, + promising: 1, + create: 1, + 400000: 1, + extra: 1, + jobs: 1, + by: 1, + 2030: 1, + }) + ); + expect( + countWords( + 'Plumbers, electricians and welders are among 31 priority occupations that are "particularly in demand", with employment in renewable, wind, solar and nuclear expected to double to 860,000 in five years, ministers have said.' + ) + ).toStrictEqual({ + 31: 1, + 860000: 1, + in: 3, + and: 2, + are: 2, + to: 2, + plumbers: 1, + electricians: 1, + welders: 1, + among: 1, + priority: 1, + occupations: 1, + that: 1, + particularly: 1, + demand: 1, + with: 1, + employment: 1, + renewable: 1, + wind: 1, + solar: 1, + nuclear: 1, + expected: 1, + double: 1, + five: 1, + years: 1, + ministers: 1, + have: 1, + said: 1, + }); +}); diff --git a/Sprint-2/stretch/count-words.js b/Sprint-2/stretch/count-words.js index 8e85d19d7..8fecd4d77 100644 --- a/Sprint-2/stretch/count-words.js +++ b/Sprint-2/stretch/count-words.js @@ -26,3 +26,23 @@ 3. Order the results to find out which word is the most common in the input */ + +function countWords(string) { + if (typeof string !== "string") { + throw new Error("Input should be a string"); + } + if (string === "") return {}; + const strArray = string + .replace(/[\.\,\!\?\"]/g, "") + .toLowerCase() + .split(" "); + + let result = {}; + + for (element of strArray) { + result[element] = (result[element] || 0) + 1; + } + return Object.fromEntries(Object.entries(result).sort((a, b) => b[1] - a[1])); +} + +module.exports = countWords; diff --git a/Sprint-2/stretch/mode.js b/Sprint-2/stretch/mode.js index 3f7609d79..4e9cccdf6 100644 --- a/Sprint-2/stretch/mode.js +++ b/Sprint-2/stretch/mode.js @@ -8,29 +8,40 @@ // 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(); +function trackFrequency(list) { + let frequency = new Map(); for (let num of list) { if (typeof num !== "number") { continue; } - freqs.set(num, (freqs.get(num) || 0) + 1); + frequency.set(num, (frequency.get(num) || 0) + 1); } + return frequency; +} - // Find the value with the highest frequency +function highestFrequency(frequencyMap) { let maxFreq = 0; let mode; - for (let [num, freq] of freqs) { + for (let [num, freq] of frequencyMap) { if (freq > maxFreq) { mode = num; maxFreq = freq; } } - return maxFreq === 0 ? NaN : mode; } +function calculateMode(list) { + // track frequency of each value + const frequencyMap = trackFrequency(list); + + // Find the value with the highest frequency + + return highestFrequency(frequencyMap); +} + +calculateMode([1, 3, "2", 2, 3, null]); + module.exports = calculateMode; diff --git a/Sprint-2/stretch/mode.test.js b/Sprint-2/stretch/mode.test.js index ca33c28a3..7d233b801 100644 --- a/Sprint-2/stretch/mode.test.js +++ b/Sprint-2/stretch/mode.test.js @@ -13,20 +13,20 @@ const calculateMode = require("./mode.js"); describe("calculateMode()", () => { test("returns the most frequent number in an array", () => { - const nums = [2, 4, 1, 2, 3, 2, 1]; + const numArray = [2, 4, 1, 2, 3, 2, 1]; - expect(calculateMode(nums)).toEqual(2); + expect(calculateMode(numArray)).toEqual(2); }); test("returns the first mode in case of multiple modes", () => { - const nums = [1, 2, 2, 3, 3]; + const numArray = [1, 2, 2, 3, 3]; - expect(calculateMode(nums)).toEqual(2); + expect(calculateMode(numArray)).toEqual(2); }); test("ignores non-number values", () => { - const nums = [1, 3, "2", 2, 3, null]; + const numArray = [1, 3, "2", 2, 3, null]; - expect(calculateMode(nums)).toEqual(3); + expect(calculateMode(numArray)).toEqual(3); }); }); diff --git a/Sprint-2/stretch/till.js b/Sprint-2/stretch/till.js index 6a08532e7..beb677f44 100644 --- a/Sprint-2/stretch/till.js +++ b/Sprint-2/stretch/till.js @@ -5,13 +5,24 @@ // Then it should return the total amount in pounds function totalTill(till) { + if (typeof till !== "object" || Array.isArray(till) || till === null) { + throw new Error("Input should be an object"); + } + + const validPence = ["1p", "5p", "20p", "50p"]; + let total = 0; + console.log(Object.entries(till)); + for (const [coin, quantity] of Object.entries(till)) { - total += coin * quantity; + if (validPence.indexOf(coin) === -1) { + continue; + } + total += Number(coin.slice(0, coin.length - 1)) * quantity; } - return `£${total / 100}`; + return `£${Math.floor(total / 100)}.${String(total % 100).padEnd(2, 0)}`; } const till = { @@ -22,10 +33,18 @@ const till = { }; const totalAmount = totalTill(till); +module.exports = totalTill; + // a) What is the target output when totalTill is called with the till object +// The target output is a string that state the total value of pound after converted from pence +// For example: '£4.40' // b) Why do we need to use Object.entries inside the for...of loop in this function? +// We need to convert the object till into an array of arrays [ [ '1p', 10 ], [ '5p', 6 ], [ '50p', 4 ], [ '20p', 10 ] ] +// That will enable the loop process in the for loop. // c) What does coin * quantity evaluate to inside the for...of loop? +// coin * quantity was trying to get the value of each group of pence and add to the total +// However, coin is referencing to first element of each sub array, which is a string that can't do math operation // d) Write a test for this function to check it works and then fix the implementation of totalTill diff --git a/Sprint-2/stretch/till.test.js b/Sprint-2/stretch/till.test.js new file mode 100644 index 000000000..ba22de203 --- /dev/null +++ b/Sprint-2/stretch/till.test.js @@ -0,0 +1,48 @@ +const totalTill = require("./till"); + +test("Given a non object, it should throw error message", () => { + const errorMessage = "Input should be an object"; + expect(() => totalTill([])).toThrow(errorMessage); + expect(() => totalTill(123)).toThrow(errorMessage); + expect(() => totalTill("123")).toThrow(errorMessage); + expect(() => totalTill(null)).toThrow(errorMessage); + expect(() => totalTill(undefined)).toThrow(errorMessage); + expect(() => totalTill(true)).toThrow(errorMessage); +}); + +test("Given an empty object, it should return £0.00", () => { + expect(totalTill({})).toBe("£0.00"); +}); + +test("Given a pence object, it should return value of pound", () => { + const till = { + "1p": 10, + "5p": 6, + "50p": 4, + "20p": 10, + }; + expect(totalTill(till)).toBe("£4.40"); +}); + +test("Given a large amount of pence object, it should return proper value of pound", () => { + const till = { + "1p": 1234, + "5p": 567, + "50p": 890, + "20p": 1011, + }; + expect(totalTill(till)).toBe("£687.89"); +}); + +test("Given a pence object mixed with something else, it should ignore invalid input and return the value of pound", () => { + const till = { + "1p": 10, + "5p": 6, + fakeCoin: 10, + undefined: 1, + null: 2, + "50p": 4, + "20p": 10, + }; + expect(totalTill(till)).toBe("£4.40"); +});