Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
51 changes: 28 additions & 23 deletions challenges/challenge-cowsay-two/solution1.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,35 @@
// =================
// Stripped down cowsayer CLI,
// no libraries
// https://nodejs.dev/learn/nodejs-accept-arguments-from-the-command-line
// =================
// cowsay.js

// 1. Accept arguments
const userInput = process.argv.slice(2).join(' ');

// 2. Make speech bubble
function makeBubble(text) {
if (!text) text = "Moo!";
const len = text.length;
const top = ' ' + '_'.repeat(len + 2);
const middle = `| ${text} |`;
const bottom = ' ' + '-'.repeat(len + 2);
return `${top}\n${middle}\n${bottom}`;
}

// how will you accept arguments?

// 2. Make supplies for our speech bubble

let topLine = '_';
let bottomLine = '-';
let saying = '';

// 3. Make a cow that takes a string
// 3. Make a simple cow
function makeCow() {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

curious. how did you get the image of the cow?

return `
\\ ^__^
\\ (oo)\\_______
(__)\\ )\\/\\
||----w |
|| ||
`;
}

// 4. Combine bubble + cow
function cowsay(saying) {
// how will you make the speech bubble contain the text?

// where will the cow picture go?

// how will you account for the parameter being empty?

const bubble = makeBubble(saying);
const cow = makeCow();
return bubble + cow;
}

//4. Pipe argument into cowsay function and return a cow

// how will you log this to the console?
// 5. Log to console
console.log(cowsay(userInput));
40 changes: 27 additions & 13 deletions challenges/challenge-cowsay-two/solution2.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
// =================
// Stripped down cowsayer CLI,
// no libraries or arguments
// https://nodejs.dev/learn/accept-input-from-the-command-line-in-nodejs
// =================
const readline = require('readline');

// 1. Make a command line interface.
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});

// 2. Make supplies for our speech bubble

// 3. Make a cow that takes a string
function makeBubble(text) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the implementation of the bubble is clever.

if (!text) text = "Moo!";
const len = text.length;
const top = ' ' + '_'.repeat(len + 2);
const middle = `| ${text} |`;
const bottom = ' ' + '-'.repeat(len + 2);
return `${top}\n${middle}\n${bottom}`;
}

const cow = (saying) => {
// how did you make the cow before?
}
const bubble = makeBubble(saying);
const asciiCow = `
\\ ^__^
\\ (oo)\\_______
(__)\\ )\\/\\
||----w |
|| ||
`;
return bubble + asciiCow;
};

// 4. Use readline to get a string from the terminal
// (with a prompt so it's clearer what we want)
rl.question("What should the cow say? ", (answer) => {
console.log(cow(answer));
rl.close();
});
97 changes: 97 additions & 0 deletions challenges/challenge-weather-app/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// ====================
// Mini-weather App JS
// ====================

// Your working API keys
const WEATHER_API_KEY = "13940029dbb0ac27fa8e76f770798f71";

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is generally not good practice to commit secrets/keys to source control. I'm not sure if this was the recommended approach but it is something to note moving forward. you should look up environment variables. In a situation where you have environment variables set, I would need to create my own keys to test your application, so leaving your keys here makes testing/iterating quicker. but this is something to always keep in mind as you go.

const UNSPLASH_ACCESS_KEY = "1EkJLQ64exkT3BxWkvtunY6nGsyoUpmmVOIycUfobC4";

// DOM elements
const photoEl = document.getElementById("photo");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while I can tell what this variable name does by just looking at it, you should look to have more descriptive variable names. so, photoElement or photoContainer reads better than just photoEl. searchForm and searchInput are very clear and their use case can be inferred at first glance, so keep the clarity consistent.

const thumbsEl = document.getElementById("thumbs");
const conditionsEl = document.getElementById("conditions");
const creditUserEl = document.getElementById("credit-user");
const searchForm = document.getElementById("search");
const searchInput = document.getElementById("search-tf");

// Fetch weather data from OpenWeather
async function getWeather(city = "London") {
const url = `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${WEATHER_API_KEY}&units=metric`;
const res = await fetch(url);
if (!res.ok) throw new Error("Invalid API key or city");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good response check here. however, the error description may have nothing to do with why the fetch request has failed. it could be a server timeout, it could be a 404 not found, it could be anything. but your returned description assumes that it is either the api key or city. if you want to match the error with the description, you would need to handle different response status codes.

therefore, if you want to use a generic response here, you would be doing something like:

if (!res.ok) {
  throw new Error(`request failed with status ${res.status} (${res.statusText})`);
}

const data = await res.json();
return data;
}

// Fetch images from Unsplash
async function getImages(query) {
const url = `https://api.unsplash.com/search/photos?query=${query}&client_id=${UNSPLASH_ACCESS_KEY}&per_page=5`;
const res = await fetch(url);
const data = await res.json();
return data.results;
}

// Display main photo and thumbnails
function displayImages(images) {
if (!images.length) return;

const mainImage = images[0];
photoEl.style.backgroundImage = `url(${mainImage.urls.regular})`;
creditUserEl.textContent = mainImage.user.name;
creditUserEl.href = mainImage.user.links.html;

thumbsEl.innerHTML = "";

images.forEach((img, index) => {
const thumb = document.createElement("div");
thumb.classList.add("thumb");
if (index === 0) thumb.classList.add("active");
thumb.style.backgroundImage = `url(${img.urls.thumb})`;
thumb.dataset.full = img.urls.regular;
thumb.dataset.userName = img.user.name;
thumb.dataset.userLink = img.user.links.html;

thumb.addEventListener("click", () => {
photoEl.style.backgroundImage = `url(${img.urls.regular})`;
creditUserEl.textContent = img.user.name;
creditUserEl.href = img.user.links.html;

document.querySelectorAll(".thumb").forEach(t => t.classList.remove("active"));
thumb.classList.add("active");
});

thumbsEl.appendChild(thumb);
});
}

// Display weather info
function displayWeather(weatherData) {
const description = weatherData.weather[0].description;
const temp = Math.round(weatherData.main.temp);
const cityName = weatherData.name;
conditionsEl.textContent = `${description}, ${temp}°C in ${cityName}`;
}

// Update city
async function updateCity(city) {
try {
const weather = await getWeather(city);
displayWeather(weather);
const images = await getImages(weather.weather[0].description);
displayImages(images);
} catch (err) {
console.error(err);
conditionsEl.textContent = "Error fetching weather or images!";
}
}

// Load default city
updateCity("London");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

your code says async function getWeather(city = "London"), if you are passing "London" as a parameter to the getWeather function then having city = "London" in the function declaration is redundant. it's not the end of the world btw. your implementation is very good, you only need to do getWeather(city) and then pass "London" as a parameter so it loads as your default city.


// Handle search form
searchForm.addEventListener("submit", (e) => {
e.preventDefault();
const city = searchInput.value.trim();
if (city) updateCity(city);
searchInput.value = "";
});
1 change: 1 addition & 0 deletions challenges/challenge-weather-app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ <h1 class="title">
</main>

<!-- JS goes here -->
<script src="app.js"></script>
</body>

</html>
17 changes: 17 additions & 0 deletions challenges/dog-photo-gallery/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dog Photo Gallery</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<h1>🐕 Random Dog Photo Gallery</h1>
<button id="add-dog">Add Dog</button>
<button id="clear-gallery">Clear Gallery</button>
<ul id="dog-gallery"></ul>

<script src="script.js"></script>
</body>
</html>
33 changes: 33 additions & 0 deletions challenges/dog-photo-gallery/script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const addDogBtn = document.getElementById("add-dog");
const clearGalleryBtn = document.getElementById("clear-gallery");
const gallery = document.getElementById("dog-gallery");

async function fetchDogImage() {
try {
const response = await fetch("https://dog.ceo/api/breeds/image/random");
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see (from my comments in the weather app), you have handled the !response.ok excellently here.

}
const data = await response.json();

// Create elements
const li = document.createElement("li");
const img = document.createElement("img");
img.src = data.message;
img.alt = "Cute Dog";

// Append to list
li.appendChild(img);
gallery.appendChild(li);

} catch (error) {
console.error("Error fetching dog image:", error);
alert("🐶 Oops! Failed to fetch a dog image. Try again.");
}
}

// Event listeners
addDogBtn.addEventListener("click", fetchDogImage);
clearGalleryBtn.addEventListener("click", () => {
gallery.innerHTML = "";
});
38 changes: 38 additions & 0 deletions challenges/dog-photo-gallery/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
body {
font-family: Arial, sans-serif;
text-align: center;
padding: 20px;
}

button {
margin: 10px;
padding: 10px 20px;
cursor: pointer;
border-radius: 8px;
border: none;
background: #4CAF50;
color: white;
font-size: 16px;
}

button:hover {
background: #45a049;
}

ul {
list-style: none;
padding: 0;
display: flex;
flex-wrap: wrap;
justify-content: center;
}

li {
margin: 10px;
}

img {
max-width: 200px;
border-radius: 12px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
14 changes: 14 additions & 0 deletions challenges/unit-testing/katas-tdd/calculator/calculator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export function add(numbers) {
if (numbers === "") return 0;

const parts = numbers.split(",").map(Number);
const negatives = parts.filter((num) => num < 0);

if (negatives.length > 0) {
throw new Error(`negatives not allowed: ${negatives.join(",")}`);
}

return parts
.filter((num) => num <= 1000) // ignore > 1000
.reduce((sum, num) => sum + num, 0);
}
15 changes: 15 additions & 0 deletions challenges/unit-testing/katas-tdd/calculator/calculator.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { add } from "./calculator";

describe("Calculator", () => {
test("should return 0 for empty string", () => {
expect(add("")).toBe(0);
});

test("should return the number itself when only one number is given", () => {
expect(add("5")).toBe(5);
});

test("should return sum of two numbers", () => {
expect(add("3,6")).toBe(9);
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very good add function and well-written tests. no notes.

});
15 changes: 15 additions & 0 deletions challenges/unit-testing/katas-tdd/password-verifier/verify.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export function verify(password) {
if (!password || password.length < 8) {
return "Password rejected";
}

if (!/[A-Z]/.test(password)) {
return "Password rejected";
}

if (!/[0-9]/.test(password)) {
return "Password rejected";
}

return "Password accepted";
}
25 changes: 25 additions & 0 deletions challenges/unit-testing/katas-tdd/password-verifier/verify.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { verify } from "./verify.js";

describe("Password Verifier", () => {

test("rejects password shorter than 8 characters", () => {
expect(verify("Ab1")).toBe("Password rejected");
});

test("rejects null password", () => {
expect(verify(null)).toBe("Password rejected");
});

test("rejects password without uppercase letter", () => {
expect(verify("password1")).toBe("Password rejected");
});

test("rejects password without number", () => {
expect(verify("Password")).toBe("Password rejected");
});

test("accepts valid password", () => {
expect(verify("Password1")).toBe("Password accepted");
});

});
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
function convertToNewRoman(n) {}
function convertToNewRoman(n) {
// Step 1: simple mapping for 1–10 as example
const romanNumerals = {
1: "I",
2: "II",
3: "III",
4: "IV",
5: "V",
6: "VI",
7: "VII",
8: "VIII",
9: "IX",
10: "X",
};

return romanNumerals[n] || "";
}

module.exports = convertToNewRoman;

Loading