diff --git a/.gitignore b/.gitignore index c62043e2d..17bfeb34a 100644 --- a/.gitignore +++ b/.gitignore @@ -123,3 +123,4 @@ dist .disclaimer @assignment* .dist/ +Assignments/ diff --git a/.test-summary/TEST_SUMMARY.md b/.test-summary/TEST_SUMMARY.md new file mode 100644 index 000000000..2fbbfa948 --- /dev/null +++ b/.test-summary/TEST_SUMMARY.md @@ -0,0 +1,12 @@ +## Test Summary + +**Mentors**: For more information on how to review homework assignments, please refer to the [Review Guide](https://github.com/HackYourFuture/mentors/blob/main/assignment-support/review-guide.md). + +### 3-UsingAPIs - Week2 + +| Exercise | Passed | Failed | ESLint | +|-------------------|--------|--------|--------| +| ex1-programmerFun | 5 | - | ✓ | +| ex2-pokemonApp | 5 | - | ✓ | +| ex3-rollAnAce | 7 | - | ✓ | +| ex4-diceRace | 7 | - | ✓ | diff --git a/3-UsingAPIs/Week2/assignment/ex1-programmerFun/index.js b/3-UsingAPIs/Week2/assignment/ex1-programmerFun/index.js index a99ca177b..41dc938af 100644 --- a/3-UsingAPIs/Week2/assignment/ex1-programmerFun/index.js +++ b/3-UsingAPIs/Week2/assignment/ex1-programmerFun/index.js @@ -17,28 +17,46 @@ Full description at: https://github.com/HackYourFuture/Assignments/blob/main/3-U should result in a network (DNS) error. ------------------------------------------------------------------------------*/ function requestData(url) { - // TODO return a promise using `fetch()` + return fetch(url).then((response) => { + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + return response.json(); + }); } function renderImage(data) { - // TODO render the image to the DOM - console.log(data); + const img = document.createElement('img'); + img.src = data.img; + img.alt = data.title; + img.style.maxWidth = '80%'; + img.style.display = 'block'; + img.style.margin = '20px auto'; + + const title = document.createElement('h2'); + title.textContent = data.title; + title.style.textAlign = 'center'; + + document.body.appendChild(title); + document.body.appendChild(img); } function renderError(error) { - // TODO render the error to the DOM - console.log(error); + const h1 = document.createElement('h1'); + h1.textContent = `Something went wrong : ${error.message}`; + h1.style.color = 'red'; + h1.style.textAlign = 'center'; + document.body.appendChild(h1); } -// TODO refactor with async/await and try/catch -function main() { - requestData('https://xkcd.now.sh/?comic=latest') - .then((data) => { - renderImage(data); - }) - .catch((error) => { - renderError(error); - }); +async function main() { + const url = 'https://xkcd.now.sh/?comic=latest'; + try { + const data = await requestData(url); + renderImage(data); + } catch (error) { + renderError(error); + } } window.addEventListener('load', main); diff --git a/3-UsingAPIs/Week2/assignment/ex2-pokemonApp/index.js b/3-UsingAPIs/Week2/assignment/ex2-pokemonApp/index.js index 262113997..54296bf32 100644 --- a/3-UsingAPIs/Week2/assignment/ex2-pokemonApp/index.js +++ b/3-UsingAPIs/Week2/assignment/ex2-pokemonApp/index.js @@ -21,18 +21,105 @@ Use async/await and try/catch to handle promises. Try and avoid using global variables. As much as possible, try and use function parameters and return values to pass data back and forth. ------------------------------------------------------------------------------*/ -function fetchData(/* TODO parameter(s) go here */) { - // TODO complete this function + +function createUI() { + const app = document.getElementById('app') || document.body; + + const title = document.createElement('h1'); + title.textContent = 'Pokémon App'; + + const statusEl = document.createElement('p'); + statusEl.id = 'status'; + statusEl.textContent = 'Click "Load Pokémons" to start.'; + + const loadBtn = document.createElement('button'); + loadBtn.id = 'load-btn'; + loadBtn.textContent = 'Load Pokémons'; + + const selectEl = document.createElement('select'); + selectEl.id = 'pokemon-select'; + selectEl.disabled = true; + const placeholder = document.createElement('option'); + placeholder.value = ''; + placeholder.textContent = '-- choose a Pokémon --'; + selectEl.appendChild(placeholder); + + const imgEl = document.createElement('img'); + imgEl.id = 'pokemon-image'; + imgEl.alt = 'Selected Pokémon'; + imgEl.style.maxWidth = '200px'; + imgEl.style.display = 'block'; + imgEl.style.marginTop = '12px'; + + app.append(title, statusEl, loadBtn, selectEl, imgEl); + return { loadBtn, selectEl, imgEl, statusEl }; +} + +async function fetchData(url) { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + return response.json(); +} + +function capitalize(s) { + return s ? s[0].toUpperCase() + s.slice(1) : s; } -function fetchAndPopulatePokemons(/* TODO parameter(s) go here */) { - // TODO complete this function +async function fetchAndPopulatePokemons(apiUrl, selectEl) { + const data = await fetchData(apiUrl); + selectEl.innerHTML = ''; + const placeholder = document.createElement('option'); + placeholder.value = ''; + placeholder.textContent = '-- choose a Pokémon --'; + selectEl.appendChild(placeholder); + + data.results.forEach((p) => { + const opt = document.createElement('option'); + opt.value = p.url; + opt.textContent = capitalize(p.name); + selectEl.appendChild(opt); + }); + + selectEl.disabled = false; } -function fetchImage(/* TODO parameter(s) go here */) { - // TODO complete this function +async function fetchImage(detailsUrl, imgEl) { + const pokemon = await fetchData(detailsUrl); + const sprite = pokemon?.sprites?.front_default || ''; + imgEl.src = sprite; + imgEl.alt = capitalize(pokemon.name || 'Pokémon'); } -function main() { - // TODO complete this function +async function main() { + const API_URL = 'https://pokeapi.co/api/v2/pokemon?limit=150'; + const { loadBtn, selectEl, imgEl, statusEl } = createUI(); + + loadBtn.addEventListener('click', async () => { + loadBtn.disabled = true; + statusEl.textContent = 'Loading pokémons...'; + try { + await fetchAndPopulatePokemons(API_URL, selectEl); + statusEl.textContent = 'Choose a Pokémon from the list.'; + } catch (err) { + statusEl.textContent = `Error: ${err.message}`; + } finally { + loadBtn.disabled = false; + } + }); + + selectEl.addEventListener('change', async (e) => { + const url = e.target.value; + if (!url) return; + statusEl.textContent = 'Loading Pokémon details...'; + try { + await fetchImage(url, imgEl); + statusEl.textContent = 'Loaded!'; + } catch (err) { + statusEl.textContent = `Error: ${err.message}`; + } + }); } + +window.addEventListener('load', main); diff --git a/3-UsingAPIs/Week2/assignment/ex3-rollAnAce.js b/3-UsingAPIs/Week2/assignment/ex3-rollAnAce.js index 861b31047..ec27f5eb3 100644 --- a/3-UsingAPIs/Week2/assignment/ex3-rollAnAce.js +++ b/3-UsingAPIs/Week2/assignment/ex3-rollAnAce.js @@ -17,21 +17,21 @@ import { rollDie } from '../../helpers/pokerDiceRoller.js'; * @param {DieFace} desiredValue * @returns {Promise} */ -export function rollDieUntil(desiredValue) { - // TODO rewrite this function using async/await - return rollDie().then((value) => { - if (value !== desiredValue) { - return rollDieUntil(desiredValue); - } - return value; - }); +export async function rollDieUntil(desiredValue) { + let value; + while (value !== desiredValue) { + value = await rollDie(); + } + return value; } -// TODO refactor this function to use try/catch -function main() { - rollDieUntil('ACE') - .then((results) => console.log('Resolved!', results)) - .catch((error) => console.log('Rejected!', error.message)); +async function main() { + try { + const result = await rollDieUntil('ACE'); + console.log('Resolved!', result); + } catch (error) { + console.log('Rejected!', error.message); + } } // ! Do not change or remove the code below diff --git a/3-UsingAPIs/Week2/assignment/ex4-diceRace.js b/3-UsingAPIs/Week2/assignment/ex4-diceRace.js index ddff3242c..6df9c3871 100644 --- a/3-UsingAPIs/Week2/assignment/ex4-diceRace.js +++ b/3-UsingAPIs/Week2/assignment/ex4-diceRace.js @@ -13,22 +13,28 @@ Full description at: https://github.com/HackYourFuture/Assignments/blob/main/3-U import { rollDie } from '../../helpers/pokerDiceRoller.js'; /** @import {DieFace} from "../../helpers/pokerDiceRoller.js" */ -export function rollDice() { +export async function rollDice() { const dice = [1, 2, 3, 4, 5]; - // TODO complete this function; use Promise.race() and rollDie() - rollDie(1); // TODO placeholder: modify as appropriate + const dicePromises = dice.map((die) => rollDie(die)); + const winner = await Promise.race(dicePromises); + return winner; } -// Refactor this function to use async/await and try/catch -function main() { - rollDice() - .then((results) => console.log('Resolved!', results)) - .catch((error) => console.log('Rejected!', error.message)); +async function main() { + try { + const result = await rollDice(); + console.log('Resolved!', result); + } catch (error) { + console.log('Rejected!', error.message); + } } -// ! Do not change or remove the code below if (process.env.NODE_ENV !== 'test') { main(); } -// TODO Replace this comment by your explanation that was asked for in the assignment description. +/* +Explanation: +Some dice keep rolling because Promise.race() only stops for the first finished promise. +The other promises keep running in the background. +*/ diff --git a/3-UsingAPIs/Week2/assignment/ex5-vscDebug.js b/3-UsingAPIs/Week2/assignment/ex5-vscDebug.js index a65448e57..c81e1e65e 100644 --- a/3-UsingAPIs/Week2/assignment/ex5-vscDebug.js +++ b/3-UsingAPIs/Week2/assignment/ex5-vscDebug.js @@ -11,18 +11,23 @@ async function getData(url) { function renderLaureate({ knownName, birth, death }) { console.log(`\nName: ${knownName.en}`); console.log(`Birth: ${birth.date}, ${birth.place.locationString}`); +if (death) { console.log(`Death: ${death.date}, ${death.place.locationString}`); } +} + function renderLaureates(laureates) { laureates.forEach(renderLaureate); } async function fetchAndRender() { try { - const laureates = getData( - 'http://api.nobelprize.org/2.0/laureates?birthCountry=Netherlands&format=json&csvLang=en' - ); + const { laureates } = await getData( + 'https://api.nobelprize.org/2.0/laureates?birthCountry=Netherlands&format=json&csvLang=en' +); +renderLaureates(laureates); + renderLaureates(laureates); } catch (err) { console.error(`Something went wrong: ${err.message}`); diff --git a/3-UsingAPIs/Week2/assignment/ex6-browserDebug/index.js b/3-UsingAPIs/Week2/assignment/ex6-browserDebug/index.js index 91e0402be..fe3e63525 100644 --- a/3-UsingAPIs/Week2/assignment/ex6-browserDebug/index.js +++ b/3-UsingAPIs/Week2/assignment/ex6-browserDebug/index.js @@ -2,6 +2,7 @@ Full description at:https://github.com/HackYourFuture/Assignments/blob/main/3-UsingAPIs/Week2/README.md#exercise-6-using-the-browser-debugger */ + async function getData(url) { const response = await fetch(url); return response.json(); @@ -29,9 +30,22 @@ function addTableRow(table, label, value) { function renderLaureate(ul, { knownName, birth, death }) { const li = createAndAppend('li', ul); const table = createAndAppend('table', li); - addTableRow(table, 'Name', knownName.en); - addTableRow(table, 'Birth', `${birth.date}, ${birth.place.locationString}`); - addTableRow(table, 'Death', `${death.date}, ${death.place.locationString}`); + + const name = knownName?.en ?? '(unknown)'; + const birthPlace = + typeof birth?.place?.locationString === 'string' + ? birth.place.locationString + : birth?.place?.locationString?.en ?? '—'; + addTableRow(table, 'Name', name); + addTableRow(table, 'Birth', `${birth?.date ?? '—'}, ${birthPlace}`); + + if (death) { + const deathPlace = + typeof death?.place?.locationString === 'string' + ? death.place.locationString + : death?.place?.locationString?.en ?? '—'; + addTableRow(table, 'Death', `${death?.date ?? '—'}, ${deathPlace}`); + } } function renderLaureates(laureates) { @@ -41,7 +55,7 @@ function renderLaureates(laureates) { async function fetchAndRender() { try { - const laureates = getData( + const { laureates } = await getData( 'https://api.nobelprize.org/2.0/laureates?birthCountry=Netherlands&format=json&csvLang=en' ); renderLaureates(laureates); diff --git a/3-UsingAPIs/Week2/test-reports/ex1-programmerFun.report.txt b/3-UsingAPIs/Week2/test-reports/ex1-programmerFun.report.txt new file mode 100644 index 000000000..baf490ea8 --- /dev/null +++ b/3-UsingAPIs/Week2/test-reports/ex1-programmerFun.report.txt @@ -0,0 +1,17 @@ +*** Unit Test Error Report *** + + PASS .dist/3-UsingAPIs/Week2/unit-tests/ex1-programmerFun.test.js + api-wk2-ex1-programmerFun + ✅ HTML should be syntactically valid (134 ms) + ✅ should have all TODO comments removed + ✅ should use `fetch()` + ✅ should use async/wait + ✅ should use try/catch (1 ms) + +Test Suites: 1 passed, 1 total +Tests: 5 passed, 5 total +Snapshots: 0 total +Time: 4.131 s +Ran all test suites matching /C:\\Users\\Rimha\\Assignments-Cohort54\\.dist\\3-UsingAPIs\\Week2\\unit-tests\\ex1-programmerFun.test.js/i. +No linting errors detected. +No spelling errors detected. diff --git a/3-UsingAPIs/Week2/test-reports/ex2-pokemonApp.report.txt b/3-UsingAPIs/Week2/test-reports/ex2-pokemonApp.report.txt new file mode 100644 index 000000000..e8de18a02 --- /dev/null +++ b/3-UsingAPIs/Week2/test-reports/ex2-pokemonApp.report.txt @@ -0,0 +1,23 @@ +*** Unit Test Error Report *** + + PASS .dist/3-UsingAPIs/Week2/unit-tests/ex2-pokemonApp.test.js + api-wk2-ex2-pokemonApp + ✅ HTML should be syntactically valid (131 ms) + ✅ should have all TODO comments removed + ✅ should use `fetch()` (1 ms) + ✅ should use `await fetch()` + ✅ should use try/catch + +Test Suites: 1 passed, 1 total +Tests: 5 passed, 5 total +Snapshots: 0 total +Time: 3.635 s, estimated 4 s +Ran all test suites matching /C:\\Users\\Rimha\\Assignments-Cohort54\\.dist\\3-UsingAPIs\\Week2\\unit-tests\\ex2-pokemonApp.test.js/i. +No linting errors detected. + + +*** Spell Checker Report *** + +3-UsingAPIs/Week2/assignment/ex2-pokemonApp/index.js:33:39 - Unknown word (Pokémons) +3-UsingAPIs/Week2/assignment/ex2-pokemonApp/index.js:37:31 - Unknown word (Pokémons) +3-UsingAPIs/Week2/assignment/ex2-pokemonApp/index.js:101:37 - Unknown word (pokémons) diff --git a/3-UsingAPIs/Week2/test-reports/ex3-rollAnAce.report.txt b/3-UsingAPIs/Week2/test-reports/ex3-rollAnAce.report.txt new file mode 100644 index 000000000..a5e33e0a3 --- /dev/null +++ b/3-UsingAPIs/Week2/test-reports/ex3-rollAnAce.report.txt @@ -0,0 +1,19 @@ +*** Unit Test Error Report *** + + PASS .dist/3-UsingAPIs/Week2/unit-tests/ex3-rollAnAce.test.js + api-wk2-ex3-rollAnAce + ✅ should have all TODO comments removed (2 ms) + ✅ `rollDieUntil` should not contain unneeded console.log calls + ✅ should not include a recursive call (1 ms) + ✅ should use async/wait + ✅ should use try/catch + ✅ should resolve as soon as a die settles on an ACE (23 ms) + ✅ should reject with an Error when a die rolls off the table (102 ms) + +Test Suites: 1 passed, 1 total +Tests: 7 passed, 7 total +Snapshots: 0 total +Time: 0.899 s, estimated 1 s +Ran all test suites matching /C:\\Users\\Rimha\\Assignments-Cohort54\\.dist\\3-UsingAPIs\\Week2\\unit-tests\\ex3-rollAnAce.test.js/i. +No linting errors detected. +No spelling errors detected. diff --git a/3-UsingAPIs/Week2/test-reports/ex4-diceRace.report.txt b/3-UsingAPIs/Week2/test-reports/ex4-diceRace.report.txt new file mode 100644 index 000000000..4a5dfad7e --- /dev/null +++ b/3-UsingAPIs/Week2/test-reports/ex4-diceRace.report.txt @@ -0,0 +1,19 @@ +*** Unit Test Error Report *** + + PASS .dist/3-UsingAPIs/Week2/unit-tests/ex4-diceRace.test.js + api-wk2-ex4-diceRace + ✅ should exist and be executable (2 ms) + ✅ should have all TODO comments removed (1 ms) + ✅ `rollDice` should not contain unneeded console.log calls (1 ms) + ✅ should use `dice.map()` + ✅ should use `Promise.race()` + ✅ should resolve as soon as a die settles successfully (12 ms) + ✅ should reject with an Error as soon as a die rolls off the table (71 ms) + +Test Suites: 1 passed, 1 total +Tests: 7 passed, 7 total +Snapshots: 0 total +Time: 0.875 s, estimated 1 s +Ran all test suites matching /C:\\Users\\Rimha\\Assignments-Cohort54\\.dist\\3-UsingAPIs\\Week2\\unit-tests\\ex4-diceRace.test.js/i. +No linting errors detected. +No spelling errors detected. diff --git a/3-UsingAPIs/Week2/test-reports/ex5-vscDebug.report.txt b/3-UsingAPIs/Week2/test-reports/ex5-vscDebug.report.txt new file mode 100644 index 000000000..d985f405c --- /dev/null +++ b/3-UsingAPIs/Week2/test-reports/ex5-vscDebug.report.txt @@ -0,0 +1,3 @@ +A unit test file was not provided for this exercise. +No linting errors detected. +No spelling errors detected. diff --git a/3-UsingAPIs/Week2/test-reports/ex6-browserDebug.report.txt b/3-UsingAPIs/Week2/test-reports/ex6-browserDebug.report.txt new file mode 100644 index 000000000..d985f405c --- /dev/null +++ b/3-UsingAPIs/Week2/test-reports/ex6-browserDebug.report.txt @@ -0,0 +1,3 @@ +A unit test file was not provided for this exercise. +No linting errors detected. +No spelling errors detected.