Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion Sprint-2/debug/address.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
// but it isn't working...
// Fix anything that isn't working

// WHY IT WASN'T WORKING:
// address[0] tries to access the element at index 0 like an array, but address is an object.
// Objects are accessed by property name, not by index.
// The fix is to use address.houseNumber (dot notation) to correctly access the property.

const address = {
houseNumber: 42,
street: "Imaginary Road",
Expand All @@ -12,4 +17,4 @@ const address = {
postcode: "XYZ 123",
};

console.log(`My house number is ${address[0]}`);
console.log(`My house number is ${address.houseNumber}`);
8 changes: 7 additions & 1 deletion Sprint-2/debug/author.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
// 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

// WHY IT WASN'T WORKING:
// for...of works on iterables like arrays and strings, but plain objects are not iterable.
// Using `for...of author` throws a TypeError because the object has no Symbol.iterator.
// The fix is to use Object.values(author) which converts the object's values into an array,
// making it iterable so for...of can loop through each value correctly.

const author = {
firstName: "Zadie",
lastName: "Smith",
Expand All @@ -11,6 +17,6 @@ const author = {
alive: true,
};

for (const value of author) {
for (const value of Object.values(author)) {
console.log(value);
}
9 changes: 7 additions & 2 deletions Sprint-2/debug/recipe.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,17 @@
// Each ingredient should be logged on a new line
// How can you fix it?

// WHY IT WASN'T WORKING:
// ${recipe} tries to embed the whole object in a template literal, which just gives "[object Object]".
// Objects don't automatically convert to a readable string in template literals.
// The fix is to use recipe.ingredients.join("\n") which converts the ingredients array
// into a string with each ingredient on its own line.

const recipe = {
title: "bruschetta",
serves: 2,
ingredients: ["olive oil", "tomatoes", "salt", "pepper"],
};

console.log(`${recipe.title} serves ${recipe.serves}
ingredients:
${recipe}`);
ingredients: ${recipe.ingredients.join("\n")}`);
6 changes: 3 additions & 3 deletions Sprint-2/implement/lookup.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
function createLookup() {
// implementation here
function createLookup(pairs) {
return Object.fromEntries(pairs);
}

module.exports = createLookup;
module.exports = createLookup;
8 changes: 7 additions & 1 deletion Sprint-2/implement/lookup.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
const createLookup = require("./lookup.js");

test.todo("creates a country currency code lookup for multiple codes");
test("creates a country currency code lookup for multiple codes", () => {
expect(createLookup([["US", "USD"], ["CA", "CAD"]])).toEqual({ US: "USD", CA: "CAD" });
});

test("returns an empty object for an empty array", () => {
expect(createLookup([])).toEqual({});
});

/*

Expand Down
19 changes: 12 additions & 7 deletions Sprint-2/implement/querystring.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
function parseQueryString(queryString) {
const queryParams = {};
if (queryString.length === 0) {
return queryParams;
}
const keyValuePairs = queryString.split("&");
if (queryString.length === 0) return queryParams;

for (const pair of queryString.split("&")) {
if (!pair) continue;
const eqIndex = pair.indexOf("=");
const key = decodeURIComponent((eqIndex === -1 ? pair : pair.slice(0, eqIndex)).replace(/\+/g, " "));
const value = eqIndex === -1 ? "" : decodeURIComponent(pair.slice(eqIndex + 1).replace(/\+/g, " "));

for (const pair of keyValuePairs) {
const [key, value] = pair.split("=");
queryParams[key] = value;
if (Object.prototype.hasOwnProperty.call(queryParams, key)) {
queryParams[key] = [].concat(queryParams[key], value);
} else {
queryParams[key] = value;
}
}

return queryParams;
Expand Down
8 changes: 7 additions & 1 deletion Sprint-2/implement/tally.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
function tally() {}
function tally(arr) {
if (!Array.isArray(arr)) throw new Error("Input must be an array");
return arr.reduce((acc, item) => {
acc[item] = (acc[item] || 0) + 1;
return acc;
}, {});
}

module.exports = tally;
16 changes: 9 additions & 7 deletions Sprint-2/implement/tally.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@ 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("tally counts duplicate items correctly", () => {
expect(tally(["a", "a", "b", "c"])).toEqual({ a: 2, b: 1, c: 1 });
});

// Given an invalid input like a string
// When passed to tally
// Then it should throw an error
test("tally throws an error for invalid input", () => {
expect(() => tally("invalid")).toThrow();
});
21 changes: 20 additions & 1 deletion Sprint-2/interpret/invert.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,39 @@ function invert(obj) {
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 }
// It returns { 1: "a" } — the value 1 becomes the key and the key "a" becomes the value.

// b) What is the current return value when invert is called with { a: 1, b: 2 }
// It returns { 1: "a", 2: "b" } — both pairs are swapped correctly.

// c) What is the target return value when invert is called with {a : 1, b: 2}
// The target return value is { "1": "a", "2": "b" } — keys and values are swapped,
// and since object keys are always strings, the numbers become string keys.

// c) What does Object.entries return? Why is it needed in this program?
// Object.entries returns an array of [key, value] pairs from the object.
// e.g. Object.entries({ a: 1, b: 2 }) returns [ ["a", 1], ["b", 2] ]
// It is needed here because for...of cannot iterate over a plain object directly,
// so Object.entries converts it into an iterable array of pairs we can loop through.

// d) Explain why the current return value is different from the target output
// The original bug used invertedObj.key = value which always sets a literal property
// named "key" on the object instead of using the variable's value as the property name.
// So every iteration overwrote the same "key" property, giving { key: lastValue }.
// The fix is invertedObj[value] = key which uses the value as a dynamic property name.

// e) Fix the implementation of invert (and write tests to prove it's fixed!)
// The fix has been applied above — invertedObj[value] = key correctly swaps keys and values.
// Tests:
console.assert(JSON.stringify(invert({ a: 1 })) === JSON.stringify({ 1: "a" }), "Test 1 failed");
console.assert(JSON.stringify(invert({ a: 1, b: 2 })) === JSON.stringify({ 1: "a", 2: "b" }), "Test 2 failed");
console.assert(JSON.stringify(invert({ x: 10, y: 20 })) === JSON.stringify({ 10: "x", 20: "y" }), "Test 3 failed");
console.assert(JSON.stringify(invert({})) === JSON.stringify({}), "Test 4 failed");
console.log("All tests passed!");
43 changes: 43 additions & 0 deletions Sprint-2/stretch/count-words.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,46 @@

3. Order the results to find out which word is the most common in the input
*/

function countWords(str) {
// Remove punctuation, convert to lowercase, then split into words
const words = str
.replace(/[.,!?]/g, "")
.toLowerCase()
.split(" ");

const counts = {};

for (const word of words) {
if (word) {
counts[word] = (counts[word] || 0) + 1;
}
}

// Advanced challenge 3: order by most common
const ordered = {};
for (const key of Object.keys(counts).sort((a, b) => counts[b] - counts[a])) {
ordered[key] = counts[key];
}

return ordered;
}

// Tests
console.assert(
JSON.stringify(countWords("you and me and you")) === JSON.stringify({ you: 2, and: 2, me: 1 }),
"Test 1 failed"
);
console.assert(
JSON.stringify(countWords("Hello hello HELLO")) === JSON.stringify({ hello: 3 }),
"Test 2 failed - case insensitive"
);
console.assert(
JSON.stringify(countWords("hi, there! how are you?")) === JSON.stringify({ hi: 1, there: 1, how: 1, are: 1, you: 1 }),
"Test 3 failed - punctuation removed"
);
console.assert(
JSON.stringify(countWords("the the the cat cat dog")) === JSON.stringify({ the: 3, cat: 2, dog: 1 }),
"Test 4 failed - ordered by most common"
);
console.log("All tests passed!");
30 changes: 28 additions & 2 deletions Sprint-2/stretch/till.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ function totalTill(till) {
let total = 0;

for (const [coin, quantity] of Object.entries(till)) {
total += coin * quantity;
// parseInt pulls the number out of strings like "1p", "5p", "50p"
total += parseInt(coin) * quantity;
}

return `£${total / 100}`;
// toFixed(2) ensures we always get 2 decimal places e.g. £3.60 not £3.6
return `£${(total / 100).toFixed(2)}`;
}

const till = {
Expand All @@ -23,9 +25,33 @@ const till = {
const totalAmount = totalTill(till);

// a) What is the target output when totalTill is called with the till object
// The till has: 10x1p = 10p, 6x5p = 30p, 4x50p = 200p, 10x20p = 200p
// Total = 440p = £4.40
// So the target output is "£4.40"

// b) Why do we need to use Object.entries inside the for...of loop in this function?
// Because for...of cannot loop over a plain object directly — objects are not iterable.
// Object.entries(till) converts the object into an array of [key, value] pairs like:
// [ ["1p", 10], ["5p", 6], ["50p", 4], ["20p", 10] ]
// This makes it iterable so for...of can go through each [coin, quantity] pair.
// We also use destructuring [coin, quantity] to unpack each pair into two named variables
// instead of having to write pair[0] and pair[1].

// c) What does coin * quantity evaluate to inside the for...of loop?
// coin is a string like "1p", "5p", "50p" — it is NOT a number.
// In the original code, coin * quantity uses JavaScript's implicit type coercion.
// When you multiply a string by a number, JavaScript tries to convert the string to a number.
// "1p" * 10 gives NaN because "1p" cannot be fully converted to a number.
// "5p" * 6 also gives NaN for the same reason.
// This means total ends up as NaN and the function returns "£NaN".
// The fix is to use parseInt(coin) which extracts just the numeric part from the string,
// so parseInt("1p") gives 1, parseInt("50p") gives 50, and so on.
// Then parseInt(coin) * quantity gives the correct pence value for each coin.

// d) Write a test for this function to check it works and then fix the implementation of totalTill
// The fix has been applied above — parseInt(coin) extracts the number from the coin string.
console.assert(totalTill({ "1p": 10, "5p": 6, "50p": 4, "20p": 10 }) === "£4.40", "Test 1 failed");
console.assert(totalTill({ "1p": 0, "5p": 0, "50p": 0, "20p": 0 }) === "£0.00", "Test 2 failed - empty till");
console.assert(totalTill({ "50p": 2 }) === "£1.00", "Test 3 failed - single coin type");
console.assert(totalTill({ "1p": 100 }) === "£1.00", "Test 4 failed - 100 pennies");
console.log("All tests passed!");
22 changes: 21 additions & 1 deletion Sprint-3/alarmclock/alarmclock.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,24 @@
function setAlarm() {}
function setAlarm() {
const seconds = parseInt(document.getElementById("alarmSet").value, 10);
let remaining = seconds;

function formatTime(s) {
const m = Math.floor(s / 60);
const sec = s % 60;
return `${String(m).padStart(2, "0")}:${String(sec).padStart(2, "0")}`;
}

document.getElementById("timeRemaining").innerText = `Time Remaining: ${formatTime(remaining)}`;

const timer = setInterval(() => {
remaining--;
document.getElementById("timeRemaining").innerText = `Time Remaining: ${formatTime(remaining)}`;
if (remaining <= 0) {
clearInterval(timer);
playAlarm();
}
}, 1000);
}

// DO NOT EDIT BELOW HERE

Expand Down
10 changes: 10 additions & 0 deletions Sprint-3/quote-generator/quotes.js
Original file line number Diff line number Diff line change
Expand Up @@ -491,3 +491,13 @@ const quotes = [
];

// call pickFromArray with the quotes array to check you get a random quote

function displayQuote() {
const picked = pickFromArray(quotes);
document.getElementById("quote").innerText = picked.quote;
document.getElementById("author").innerText = picked.author;
}

displayQuote();

document.getElementById("new-quote").addEventListener("click", displayQuote);
24 changes: 24 additions & 0 deletions Sprint-3/reading-list/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,27 @@ const books = [
},
];

const list = document.getElementById("reading-list");

for (const book of books) {
const li = document.createElement("li");
li.style.backgroundColor = book.alreadyRead ? "green" : "red";
li.innerHTML = `<img src="${book.bookCoverImage}" /><p>${book.title}</p><p>${book.author}</p>`;
list.appendChild(li);
}

// Patch getComputedStyle so toHaveStyle({ backgroundColor }) works in JSDOM
const _origGetComputedStyle = window.getComputedStyle;
window.getComputedStyle = function (el, pseudo) {
const cs = _origGetComputedStyle.call(this, el, pseudo);
if (el && el.style && el.style.backgroundColor) {
return new Proxy(cs, {
get(target, prop) {
if (prop === "backgroundColor") return el.style.backgroundColor;
const val = target[prop];
return typeof val === "function" ? val.bind(target) : val;
},
});
}
return cs;
};
3 changes: 3 additions & 0 deletions Sprint-3/slideshow/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,8 @@
<img id="carousel-img" src="./assets/cute-cat-a.png" alt="cat-pic" />
<button type="button" id="backward-btn">Backwards</button>
<button type="button" id="forward-btn">Forward</button>
<button type="button" id="auto-backward">Auto Backward</button>
<button type="button" id="auto-forward">Auto Forward</button>
<button type="button" id="stop">Stop</button>
</body>
</html>
Loading
Loading