mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
refactor(curriculum): rework pokemon project to rpg creatures project (#58812)
This commit is contained in:
@@ -127,8 +127,8 @@ const allStandardCerts = [
|
||||
},
|
||||
{
|
||||
id: '6555c1d3e11a1574434cf8b5',
|
||||
title: 'Build a Pokémon Search App',
|
||||
link: getJavaScriptAlgoPath('build-a-pokemon-search-app'),
|
||||
title: 'Build an RPG Creature Search App',
|
||||
link: getJavaScriptAlgoPath('build-an-rpg-creature-search-app'),
|
||||
certSlug: Certification.JsAlgoDataStructNew
|
||||
}
|
||||
]
|
||||
|
||||
@@ -308,11 +308,11 @@
|
||||
],
|
||||
"note": "Note: Some browser extensions, such as ad-blockers and script-blockers can interfere with the tests. If you face issues, we recommend disabling extensions that modify or block the content of pages while taking the course.",
|
||||
"blocks": {
|
||||
"build-a-pokemon-search-app-project": {
|
||||
"title": "Build a Pokémon Search App Project",
|
||||
"build-an-rpg-creature-search-app-project": {
|
||||
"title": "Build an RPG Creature Search App Project",
|
||||
"intro": [
|
||||
"This is one of the required projects to earn your certification.",
|
||||
"For this project, you will build a Pokémon search app."
|
||||
"For this project, you will build an RPG creature search app."
|
||||
]
|
||||
},
|
||||
"build-a-cash-register-project": {
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "Build a Pokémon Search App Project",
|
||||
"name": "Build an RPG Creature Search App Project",
|
||||
"isUpcomingChange": false,
|
||||
"dashedName": "build-a-pokemon-search-app-project",
|
||||
"dashedName": "build-an-rpg-creature-search-app-project",
|
||||
"usesMultifileEditor": true,
|
||||
"helpCategory": "JavaScript",
|
||||
"order": 24,
|
||||
@@ -9,7 +9,7 @@
|
||||
"challengeOrder": [
|
||||
{
|
||||
"id": "6555c1d3e11a1574434cf8b5",
|
||||
"title": "Build a Pokémon Search App Project"
|
||||
"title": "Build an RPG Creature Search App Project"
|
||||
}
|
||||
],
|
||||
"blockLayout": "legacy-link"
|
||||
+1
-1
@@ -13,4 +13,4 @@ tests:
|
||||
- id: 657bdcc3a322aae1eac38392
|
||||
title: Build a Cash Register
|
||||
- id: 6555c1d3e11a1574434cf8b5
|
||||
title: Build a Pokémon Search App
|
||||
title: Build an RPG Creature Search App
|
||||
|
||||
+156
-192
@@ -1,25 +1,25 @@
|
||||
---
|
||||
id: 6555c1d3e11a1574434cf8b5
|
||||
title: Build a Pokémon Search App
|
||||
title: Build an RPG Creature Search App
|
||||
challengeType: 14
|
||||
forumTopicId: 16003
|
||||
dashedName: build-a-pokemon-search-app
|
||||
dashedName: build-an-rpg-creature-search-app
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
In this project, you'll build an app that will search for Pokémon by name or ID and display the results to the user. To retrieve the Pokémon data and images, you'll use freeCodeCamp's <a href="https://pokeapi-proxy.freecodecamp.rocks/" target="_blank" rel="noopener noreferrer nofollow">PokéAPI Proxy</a>.
|
||||
In this project, you'll build an app that will search for creatures from an RPG game by name or ID and display the results to the user. To retrieve the creature data and images, you'll use freeCodeCamp's <a href="https://rpg-creature-api.freecodecamp.rocks/" target="_blank" rel="noopener noreferrer nofollow">RPG Creature API</a>.
|
||||
|
||||
**Note:** The first 13 steps must be completed inside the `index.html` file.
|
||||
|
||||
**Objective:** Build an app that is functionally similar to <a href="https://pokemon-search-app.freecodecamp.rocks" target="_blank" rel="noopener noreferrer nofollow">https://pokemon-search-app.freecodecamp.rocks</a>.
|
||||
**Objective:** Build an app that is functionally similar to <a href="https://rpg-creature-search-app.freecodecamp.rocks" target="_blank" rel="noopener noreferrer nofollow">https://rpg-creature-search-app.freecodecamp.rocks</a>.
|
||||
|
||||
**User Stories:**
|
||||
|
||||
1. You should have an `input` element with an `id` of `"search-input"`, and is required.
|
||||
1. You should have a `button` element with an `id` of `"search-button"`.
|
||||
1. You should have an element with an `id` of `"pokemon-name"`.
|
||||
1. You should have an element with an `id` of `"pokemon-id"`.
|
||||
1. You should have an element with an `id` of `"creature-name"`.
|
||||
1. You should have an element with an `id` of `"creature-id"`.
|
||||
1. You should have an element with an `id` of `"weight"`.
|
||||
1. You should have an element with an `id` of `"height"`.
|
||||
1. You should have an element with an `id` of `"types"`.
|
||||
@@ -29,15 +29,13 @@ In this project, you'll build an app that will search for Pokémon by name or ID
|
||||
1. You should have an element with an `id` of `"special-attack"`.
|
||||
1. You should have an element with an `id` of `"special-defense"`.
|
||||
1. You should have an element with an `id` of `"speed"`.
|
||||
1. When the `#search-input` element contains the value `Red` and the `#search-button` element is clicked, an alert should appear with the text `"Pokémon not found"`.
|
||||
1. When the `#search-input` element contains the value `Pikachu` and the `#search-button` element is clicked, the values in the `#pokemon-name`, `#pokemon-id`, `#weight`, `#height`, `#hp`, `#attack`, `#defense`, `#special-attack`, `#special-defense`, and `#speed` elements should be `PIKACHU`, `#25` or `25`, `Weight: 60` or `60`, `Height: 4` or `4`, `35`, `55`, `40`, `50`, `50`, and `90`, respectively.
|
||||
1. When the `#search-input` element contains the value `Pikachu` and the `#search-button` element is clicked, you should add an `img` element with the `id` of `"sprite"` and the `src` set to the Pokémon's `front_default` sprite to the page.
|
||||
1. When the `#search-input` element contains the value `Pikachu` and the `#search-button` element is clicked, the `#types` element should contain a single inner element with the value `ELECTRIC`. The `#types` element content should be cleared between searches.
|
||||
1. When the `#search-input` element contains the value `94` and the `#search-button` element is clicked, the values in the `#pokemon-name`, `#pokemon-id`, `#weight`, `#height`, `#hp`, `#attack`, `#defense`, `#special-attack`, `#special-defense`, and `#speed`elements should be `GENGAR`, `#94` or `94`, `Weight: 405` or `405`, `Height: 15` or `15`, `60`, `65`, `60`, `130`, `75`, and `110`, respectively.
|
||||
1. When the `#search-input` element contains the value `94` and the `#search-button` element is clicked, you should add an `img` element with the `id` of `sprite` and the `src` set to the Pokémon's `front_default` sprite to the page.
|
||||
1. When the `#search-input` element contains the value `94` and the `#search-button` element is clicked, the `#types` element should contain two inner elements with the text values `GHOST` and `POISON`, respectively. The `#types` element content should be cleared between searches.
|
||||
1. When the `#search-input` element contains an invalid Pokemon name, and the `#search-button` element is clicked, an alert should appear with the text `"Pokémon not found"`.
|
||||
1. When the `#search-input` element contains a valid Pokemon id and the `#search-button` element is clicked, the UI should be filled with the correct data.
|
||||
1. When the `#search-input` element contains the value `Red` and the `#search-button` element is clicked, an alert should appear with the text `"Creature not found"`.
|
||||
1. When the `#search-input` element contains the value `Pyrolynx` and the `#search-button` element is clicked, the values in the `#creature-name`, `#creature-id`, `#weight`, `#height`, `#hp`, `#attack`, `#defense`, `#special-attack`, `#special-defense`, and `#speed` elements should be `PYROLYNX`, `#1` or `1`, `Weight: 42` or `42`, `Height: 32` or `32`, `65`, `80`, `50`, `90`, `55`, and `100`, respectively.
|
||||
1. When the `#search-input` element contains the value `Pyrolynx` and the `#search-button` element is clicked, a single element should be added within the `#types` element that contains the text `FIRE`. The `#types` element content should be cleared between searches.
|
||||
1. When the `#search-input` element contains the value `2` and the `#search-button` element is clicked, the values in the `#creature-name`, `#creature-id`, `#weight`, `#height`, `#hp`, `#attack`, `#defense`, `#special-attack`, `#special-defense`, and `#speed` elements should be `AQUOROC`, `#2` or `2`, `Weight: 220` or `220`, `Height: 53` or `53`, `85`, `90`, `120`, `60`, `70`, and `40`, respectively.
|
||||
1. When the `#search-input` element contains the value `2` and the `#search-button` element is clicked, two elements should be added within the `#types` element that contain text values `WATER` and `ROCK`, respectively. The `#types` element content should be cleared between searches.
|
||||
1. When the `#search-input` element contains an invalid creature name, and the `#search-button` element is clicked, an alert should appear with the text `"Creature not found"`.
|
||||
1. When the `#search-input` element contains a valid creature ID and the `#search-button` element is clicked, the UI should be filled with the correct data.
|
||||
|
||||
Fulfill the user stories and pass all the tests below to complete this project. Give it your own personal style. Happy Coding!
|
||||
|
||||
@@ -60,17 +58,17 @@ const el = document.getElementById('search-button');
|
||||
assert.strictEqual(el?.nodeName?.toLowerCase(), 'button');
|
||||
```
|
||||
|
||||
You should have an element with an `id` of `"pokemon-name"`.
|
||||
You should have an element with an `id` of `"creature-name"`.
|
||||
|
||||
```js
|
||||
const el = document.getElementById('pokemon-name');
|
||||
const el = document.getElementById('creature-name');
|
||||
assert.exists(el);
|
||||
```
|
||||
|
||||
You should have an element with an `id` of `"pokemon-id"`.
|
||||
You should have an element with an `id` of `"creature-id"`.
|
||||
|
||||
```js
|
||||
const el = document.getElementById('pokemon-id');
|
||||
const el = document.getElementById('creature-id');
|
||||
assert.exists(el);
|
||||
```
|
||||
|
||||
@@ -137,7 +135,7 @@ const el = document.getElementById('speed');
|
||||
assert.exists(el);
|
||||
```
|
||||
|
||||
When the `#search-input` element contains the value `Red` and the `#search-button` element is clicked, an alert should appear with the text `"Pokémon not found"`.
|
||||
When the `#search-input` element contains the value `Red` and the `#search-button` element is clicked, an alert should appear with the text `"Creature not found"`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
@@ -150,12 +148,14 @@ async () => {
|
||||
searchInput.dispatchEvent(new Event('change'));
|
||||
searchButton.click();
|
||||
|
||||
const res = await fetch('https://pokeapi-proxy.freecodecamp.rocks/api/pokemon/red'); // Fetch from proxy to simulate network delay
|
||||
const res = await fetch('https://rpg-creature-api.freecodecamp.rocks/api/creature/red'); // Fetch from proxy to simulate network delay
|
||||
|
||||
if (!res.ok) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000)); // Additional delay to allow the alert to trigger
|
||||
|
||||
assert.include(['pokémon not found', 'pokemon not found'], alertMessage?.trim().replace(/[.,?!]+$/g, '').toLowerCase());
|
||||
assert.equal(alertMessage?.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'creature not found');
|
||||
} else {
|
||||
assert.fail();
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
@@ -163,24 +163,24 @@ async () => {
|
||||
};
|
||||
```
|
||||
|
||||
When the `#search-input` element contains the value `Pikachu` and the `#search-button` element is clicked, the values in the `#pokemon-name`, `#pokemon-id`, `#weight`, `#height`, `#hp`, `#attack`, `#defense`, `#special-attack`, `#special-defense`, and `#speed` elements should be `PIKACHU`, `#25` or `25`, `Weight: 60` or `60`, `Height: 4` or `4`, `35`, `55`, `40`, `50`, `50`, and `90`, respectively.
|
||||
When the `#search-input` element contains the value `Pyrolynx` and the `#search-button` element is clicked, the values in the `#creature-name`, `#creature-id`, `#weight`, `#height`, `#hp`, `#attack`, `#defense`, `#special-attack`, `#special-defense`, and `#speed` elements should be `PYROLYNX`, `#1` or `1`, `Weight: 42` or `42`, `Height: 32` or `32`, `65`, `80`, `50`, `90`, `55`, and `100`, respectively.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const searchInput = document.getElementById('search-input');
|
||||
const searchButton = document.getElementById('search-button');
|
||||
searchInput.value = 'Pikachu';
|
||||
searchInput.value = 'Pyrolynx';
|
||||
searchInput.dispatchEvent(new Event('change'));
|
||||
searchButton.click();
|
||||
|
||||
const res = await fetch('https://pokeapi-proxy.freecodecamp.rocks/api/pokemon/pikachu'); // Fetch from proxy to simulate network delay
|
||||
const res = await fetch('https://rpg-creature-api.freecodecamp.rocks/api/creature/Pyrolynx'); // Fetch from proxy to simulate network delay
|
||||
|
||||
if (res.ok) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000)); // Additional delay to allow UI to update
|
||||
|
||||
const pokemonName = document.getElementById('pokemon-name');
|
||||
const pokemonID = document.getElementById('pokemon-id');
|
||||
const creatureName = document.getElementById('creature-name');
|
||||
const creatureID = document.getElementById('creature-id');
|
||||
const weight = document.getElementById('weight');
|
||||
const height = document.getElementById('height');
|
||||
const hp = document.getElementById('hp');
|
||||
@@ -190,16 +190,18 @@ async () => {
|
||||
const specialDefense = document.getElementById('special-defense');
|
||||
const speed = document.getElementById('speed');
|
||||
|
||||
assert.strictEqual(pokemonName.innerText.trim().toLowerCase(), 'pikachu');
|
||||
assert.include(['#25', '25'], pokemonID.innerText.trim());
|
||||
assert.include(['weight: 60', '60'], weight.innerText.trim().toLowerCase());
|
||||
assert.include(['height: 4', '4'], height.innerText.trim().toLowerCase());
|
||||
assert.strictEqual(hp.innerText.trim(), '35');
|
||||
assert.strictEqual(attack.innerText.trim(), '55');
|
||||
assert.strictEqual(defense.innerText.trim(), '40');
|
||||
assert.strictEqual(specialAttack.innerText.trim(), '50');
|
||||
assert.strictEqual(specialDefense.innerText.trim(), '50');
|
||||
assert.strictEqual(speed.innerText.trim(), '90');
|
||||
assert.strictEqual(creatureName.innerText.trim().toLowerCase(), 'pyrolynx');
|
||||
assert.include(['#1', '1'], creatureID.innerText.trim());
|
||||
assert.include(['weight: 42', '42'], weight.innerText.trim().toLowerCase());
|
||||
assert.include(['height: 32', '32'], height.innerText.trim().toLowerCase());
|
||||
assert.strictEqual(hp.innerText.trim(), '65');
|
||||
assert.strictEqual(attack.innerText.trim(), '80');
|
||||
assert.strictEqual(defense.innerText.trim(), '50');
|
||||
assert.strictEqual(specialAttack.innerText.trim(), '90');
|
||||
assert.strictEqual(specialDefense.innerText.trim(), '55');
|
||||
assert.strictEqual(speed.innerText.trim(), '100');
|
||||
} else {
|
||||
assert.fail();
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
@@ -207,43 +209,18 @@ async () => {
|
||||
};
|
||||
```
|
||||
|
||||
When the `#search-input` element contains the value `Pikachu` and the `#search-button` element is clicked, you should add an `img` element with the `id` of `"sprite"` and the `src` set to the Pokémon's `front_default` sprite to the page.
|
||||
When the `#search-input` element contains the value `Pyrolynx` and the `#search-button` element is clicked, a single element should be added within the `#types` element that contains the text `FIRE`. The `#types` element content should be cleared between searches.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const searchInput = document.getElementById('search-input');
|
||||
const searchButton = document.getElementById('search-button');
|
||||
searchInput.value = 'Pikachu';
|
||||
searchInput.value = 'Pyrolynx';
|
||||
searchInput.dispatchEvent(new Event('change'));
|
||||
searchButton.click();
|
||||
|
||||
const res = await fetch('https://pokeapi-proxy.freecodecamp.rocks/api/pokemon/pikachu'); // Fetch from proxy to simulate network delay
|
||||
|
||||
if (res.ok) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000)); // Additional delay to allow UI to update
|
||||
|
||||
const sprite = document.getElementById('sprite');
|
||||
assert.isTrue(sprite.src.endsWith('sprites/pokemon/25.png'));
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
When the `#search-input` element contains the value `Pikachu` and the `#search-button` element is clicked, the `#types` element should contain a single inner element with the value `ELECTRIC`. Make sure the `#types` element content is cleared between searches.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const searchInput = document.getElementById('search-input');
|
||||
const searchButton = document.getElementById('search-button');
|
||||
searchInput.value = 'Pikachu';
|
||||
searchInput.dispatchEvent(new Event('change'));
|
||||
searchButton.click();
|
||||
|
||||
const res = await fetch('https://pokeapi-proxy.freecodecamp.rocks/api/pokemon/pikachu'); // Fetch from proxy to simulate network delay
|
||||
const res = await fetch('https://rpg-creature-api.freecodecamp.rocks/api/creature/Pyrolynx'); // Fetch from proxy to simulate network delay
|
||||
|
||||
if (res.ok) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000)); // Additional delay to allow UI to update
|
||||
@@ -251,7 +228,9 @@ async () => {
|
||||
const typesEl = document.getElementById('types');
|
||||
|
||||
assert.lengthOf(typesEl.children, 1);
|
||||
assert.strictEqual(typesEl?.children[0]?.innerText.trim().toLowerCase(), 'electric');
|
||||
assert.strictEqual(typesEl?.children[0]?.innerText.trim().toLowerCase(), 'fire');
|
||||
} else {
|
||||
assert.fail();
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
@@ -259,24 +238,24 @@ async () => {
|
||||
};
|
||||
```
|
||||
|
||||
When the `#search-input` element contains the value `94` and the `#search-button` element is clicked, the values in the `#pokemon-name`, `#pokemon-id`, `#weight`, `#height`, `#hp`, `#attack`, `#defense`, `#special-attack`, `#special-defense`, and `#speed` elements should be `GENGAR`, `#94` or `94`, `Weight: 405` or `405`, `Height: 15` or `15`, `60`, `65`, `60`, `130`, `75`, and `110`, respectively.
|
||||
When the `#search-input` element contains the value `2` and the `#search-button` element is clicked, the values in the `#creature-name`, `#creature-id`, `#weight`, `#height`, `#hp`, `#attack`, `#defense`, `#special-attack`, `#special-defense`, and `#speed` elements should be `AQUOROC`, `#2` or `2`, `Weight: 220` or `220`, `Height: 53` or `53`, `85`, `90`, `120`, `60`, `70`, and `40`, respectively.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const searchInput = document.getElementById('search-input');
|
||||
const searchButton = document.getElementById('search-button');
|
||||
searchInput.value = '94';
|
||||
searchInput.value = '2';
|
||||
searchInput.dispatchEvent(new Event('change'));
|
||||
searchButton.click();
|
||||
|
||||
const res = await fetch('https://pokeapi-proxy.freecodecamp.rocks/api/pokemon/94'); // Fetch from proxy to simulate network delay
|
||||
const res = await fetch('https://rpg-creature-api.freecodecamp.rocks/api/creature/2'); // Fetch from proxy to simulate network delay
|
||||
|
||||
if (res.ok) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000)); // Additional delay to allow UI to update
|
||||
|
||||
const pokemonName = document.getElementById('pokemon-name');
|
||||
const pokemonID = document.getElementById('pokemon-id');
|
||||
const creatureName = document.getElementById('creature-name');
|
||||
const creatureID = document.getElementById('creature-id');
|
||||
const weight = document.getElementById('weight');
|
||||
const height = document.getElementById('height');
|
||||
const hp = document.getElementById('hp');
|
||||
@@ -286,16 +265,18 @@ async () => {
|
||||
const specialDefense = document.getElementById('special-defense');
|
||||
const speed = document.getElementById('speed');
|
||||
|
||||
assert.strictEqual(pokemonName.innerText.trim().toLowerCase(), 'gengar');
|
||||
assert.include(['#94', '94'], pokemonID.innerText.trim());
|
||||
assert.include(['weight: 405', '405'], weight.innerText.trim().toLowerCase());
|
||||
assert.include(['height: 15', '15'], height.innerText.trim().toLowerCase());
|
||||
assert.strictEqual(hp.innerText.trim(), '60');
|
||||
assert.strictEqual(attack.innerText.trim(), '65');
|
||||
assert.strictEqual(defense.innerText.trim(), '60');
|
||||
assert.strictEqual(specialAttack.innerText.trim(), '130');
|
||||
assert.strictEqual(specialDefense.innerText.trim(), '75');
|
||||
assert.strictEqual(speed.innerText.trim(), '110');
|
||||
assert.strictEqual(creatureName.innerText.trim().toLowerCase(), 'aquoroc');
|
||||
assert.include(['#2', '2'], creatureID.innerText.trim());
|
||||
assert.include(['weight: 220', '220'], weight.innerText.trim().toLowerCase());
|
||||
assert.include(['height: 53', '53'], height.innerText.trim().toLowerCase());
|
||||
assert.strictEqual(hp.innerText.trim(), '85');
|
||||
assert.strictEqual(attack.innerText.trim(), '90');
|
||||
assert.strictEqual(defense.innerText.trim(), '120');
|
||||
assert.strictEqual(specialAttack.innerText.trim(), '60');
|
||||
assert.strictEqual(specialDefense.innerText.trim(), '70');
|
||||
assert.strictEqual(speed.innerText.trim(), '40');
|
||||
} else {
|
||||
assert.fail();
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
@@ -303,52 +284,29 @@ async () => {
|
||||
};
|
||||
```
|
||||
|
||||
When the `#search-input` element contains the value `94` and the `#search-button` element is clicked, you should add an `img` element with the `id` of `"sprite"` and the `src` set to the Pokémon's `front_default` sprite to the page.
|
||||
When the `#search-input` element contains the value `2` and the `#search-button` element is clicked, two elements should be added within the `#types` element that contain text values `WATER` and `ROCK`, respectively. The `#types` element content should be cleared between searches.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const searchInput = document.getElementById('search-input');
|
||||
const searchButton = document.getElementById('search-button');
|
||||
searchInput.value = '94';
|
||||
searchInput.value = '2';
|
||||
searchInput.dispatchEvent(new Event('change'));
|
||||
searchButton.click();
|
||||
|
||||
const res = await fetch('https://pokeapi-proxy.freecodecamp.rocks/api/pokemon/94'); // Fetch from proxy to simulate network delay
|
||||
const res = await fetch('https://rpg-creature-api.freecodecamp.rocks/api/creature/2'); // Fetch from proxy to simulate network delay
|
||||
|
||||
if (res.ok) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000)); // Additional delay to allow UI to update
|
||||
|
||||
const sprite = document.getElementById('sprite');
|
||||
assert.isTrue(sprite.src.endsWith('sprites/pokemon/94.png'));
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
When the `#search-input` element contains the value `94` and the `#search-button` element is clicked, the `#types` element should contain two inner elements with the text values `GHOST` and `POISON`, respectively. Make sure the `#types` element content is cleared between searches.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
try {
|
||||
const searchInput = document.getElementById('search-input');
|
||||
const searchButton = document.getElementById('search-button');
|
||||
searchInput.value = '94';
|
||||
searchInput.dispatchEvent(new Event('change'));
|
||||
searchButton.click();
|
||||
|
||||
const res = await fetch('https://pokeapi-proxy.freecodecamp.rocks/api/pokemon/94'); // Fetch from proxy to simulate network delay
|
||||
|
||||
if (res.ok) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000)); // Additional delay to allow UI to update
|
||||
const targetTypes = ['ghost', 'poison'];
|
||||
const targetTypes = ['water', 'rock'];
|
||||
|
||||
const typesEl = document.getElementById('types');
|
||||
|
||||
assert.lengthOf(typesEl.children, 2);
|
||||
assert.sameMembers(['ghost', 'poison'], [...typesEl.children].map(el => el.innerText.trim().toLowerCase()));
|
||||
assert.sameMembers(['water', 'rock'], [...typesEl.children].map(el => el.innerText.trim().toLowerCase()));
|
||||
} else {
|
||||
assert.fail();
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
@@ -356,7 +314,7 @@ async () => {
|
||||
};
|
||||
```
|
||||
|
||||
When the `#search-input` element contains an invalid Pokemon name and the `#search-button` element is clicked, an alert should appear with the text `"Pokémon not found"`.
|
||||
When the `#search-input` element contains an invalid creature name and the `#search-button` element is clicked, an alert should appear with the text `"Creature not found"`.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
@@ -366,18 +324,20 @@ async () => {
|
||||
let alertMessage;
|
||||
window.alert = (message) => alertMessage = message; // Override alert and store message
|
||||
|
||||
const randomInvalidPokeId = crypto.randomUUID().substring(0, 6);
|
||||
const randomInvalidCreatureId = crypto.randomUUID().substring(0, 6);
|
||||
|
||||
searchInput.value = randomInvalidPokeId;
|
||||
searchInput.value = randomInvalidCreatureId;
|
||||
searchInput.dispatchEvent(new Event('change'));
|
||||
searchButton.click();
|
||||
|
||||
const res = await fetch('https://pokeapi-proxy.freecodecamp.rocks/api/pokemon/' + randomInvalidPokeId); // Fetch from proxy to simulate network delay
|
||||
const res = await fetch('https://rpg-creature-api.freecodecamp.rocks/api/creature/' + randomInvalidCreatureId); // Fetch from proxy to simulate network delay
|
||||
|
||||
if (!res.ok) {
|
||||
await new Promise(resolve => setTimeout(resolve, 2000)); // Additional delay to allow the alert to trigger
|
||||
|
||||
assert.include(['pokémon not found', 'pokemon not found'], alertMessage?.trim().replace(/[.,?!]+$/g, '').toLowerCase());
|
||||
assert.equal(alertMessage?.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'creature not found');
|
||||
} else {
|
||||
assert.fail();
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
@@ -386,7 +346,7 @@ async () => {
|
||||
```
|
||||
|
||||
|
||||
When the `#search-input` element contains a valid Pokemon id and the `#search-button` element is clicked, the UI should be filled with the correct data.
|
||||
When the `#search-input` element contains a valid creature ID and the `#search-button` element is clicked, the UI should be filled with the correct data.
|
||||
|
||||
```js
|
||||
async () => {
|
||||
@@ -396,23 +356,25 @@ async () => {
|
||||
let alertMessage;
|
||||
window.alert = (message) => alertMessage = message; // Override alert and store message
|
||||
|
||||
const randomValidPokeId = String(Math.floor(Math.random() * 1025) + 1);
|
||||
const randomValidCreatureId = String(Math.floor(Math.random() * 20) + 1);
|
||||
|
||||
searchInput.value = randomValidPokeId;
|
||||
searchInput.value = randomValidCreatureId;
|
||||
searchInput.dispatchEvent(new Event('change'));
|
||||
searchButton.click();
|
||||
|
||||
const res = await fetch('https://pokeapi-proxy.freecodecamp.rocks/api/pokemon/' + randomValidPokeId); // Fetch from proxy to simulate network delay
|
||||
const res = await fetch('https://rpg-creature-api.freecodecamp.rocks/api/creature/' + randomValidCreatureId); // Fetch from proxy to simulate network delay
|
||||
|
||||
if (res.ok) {
|
||||
await new Promise(resolve => setTimeout(resolve, 2000)); // Additional delay to allow UI to update
|
||||
|
||||
const data = await res.json();
|
||||
const typesEl = document.getElementById('types');
|
||||
const actualTypes = data.types.map(typeSlot => typeSlot.type.name);
|
||||
const actualTypes = data.types.map(typeSlot => typeSlot.name);
|
||||
|
||||
assert.lengthOf(typesEl.children, actualTypes.length);
|
||||
assert.sameMembers(actualTypes, [...typesEl.children].map(el => el.innerText.trim().toLowerCase()));
|
||||
} else {
|
||||
assert.fail();
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
@@ -450,9 +412,10 @@ async () => {
|
||||
type="image/png"
|
||||
href="https://cdn.freecodecamp.org/universal/favicons/favicon.ico"
|
||||
/>
|
||||
<title>Pokémon Search App</title>
|
||||
<title>Creature Search App</title>
|
||||
<link rel="stylesheet" href="styles.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<img
|
||||
@@ -460,25 +423,28 @@ async () => {
|
||||
src="https://cdn.freecodecamp.org/platform/universal/fcc_primary.svg"
|
||||
alt="freeCodeCamp Logo"
|
||||
/>
|
||||
<h1>Pokémon Search App</h1>
|
||||
<h1>Creature Search App</h1>
|
||||
<div class="container">
|
||||
<form role="search" id="search-form">
|
||||
<label for="search-input">Search for Pokémon Name or ID:</label>
|
||||
<input type="text" name="pokemon" id="search-input" required />
|
||||
<label for="search-input">Search for Creature Name or ID:</label>
|
||||
<input type="text" name="creature" id="search-input" required />
|
||||
<button id="search-button">Search</button>
|
||||
</form>
|
||||
<div class="output">
|
||||
<div class="top-container">
|
||||
<div class="name-and-id">
|
||||
<span id="pokemon-name"></span>
|
||||
<span id="pokemon-id"></span>
|
||||
<span id="creature-name"></span>
|
||||
<span id="creature-id"></span>
|
||||
<div class="size">
|
||||
<span id="weight"></span>
|
||||
<span id="height"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="size">
|
||||
<span id="weight"></span>
|
||||
<span id="height"></span>
|
||||
</div>
|
||||
<div id="sprite-container" class="sprite-container"></div>
|
||||
<div id="types"></div>
|
||||
<div>
|
||||
<div id="special-name"></div>
|
||||
<div id="special-description"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bottom-container">
|
||||
<table>
|
||||
@@ -629,13 +595,20 @@ label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
min-height: 325px;
|
||||
}
|
||||
|
||||
.top-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-evenly;
|
||||
margin-bottom: 10px;
|
||||
padding: 10px;
|
||||
background-color: #f0f1f7;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.bottom-container {
|
||||
min-height: 325px;
|
||||
}
|
||||
|
||||
.name-and-id {
|
||||
@@ -645,22 +618,16 @@ label {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.size {
|
||||
height: 22px;
|
||||
#creature-name,
|
||||
#special-name {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.size,
|
||||
#special-description {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.sprite-container {
|
||||
flex-grow: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#sprite {
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
#types {
|
||||
min-height: 30px;
|
||||
display: flex;
|
||||
@@ -703,7 +670,7 @@ th {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
/* Special styling for Pokémon types */
|
||||
/* Special styling for Creature types */
|
||||
|
||||
.normal {
|
||||
background-color: #b7b7aa;
|
||||
@@ -799,37 +766,37 @@ th {
|
||||
```
|
||||
|
||||
```js
|
||||
const pokemonID = document.getElementById("pokemon-id");
|
||||
const pokemonName = document.getElementById("pokemon-name");
|
||||
const spriteContainer = document.getElementById("sprite-container");
|
||||
const types = document.getElementById("types");
|
||||
const height = document.getElementById("height");
|
||||
const weight = document.getElementById("weight");
|
||||
const hp = document.getElementById("hp");
|
||||
const attack = document.getElementById("attack");
|
||||
const defense = document.getElementById("defense");
|
||||
const specialAttack = document.getElementById("special-attack");
|
||||
const specialDefense = document.getElementById("special-defense");
|
||||
const speed = document.getElementById("speed");
|
||||
const searchForm = document.getElementById("search-form");
|
||||
const searchInput = document.getElementById("search-input");
|
||||
const creatureID = document.getElementById('creature-id');
|
||||
const creatureName = document.getElementById('creature-name');
|
||||
const specialName = document.getElementById('special-name');
|
||||
const specialDescription = document.getElementById('special-description');
|
||||
const types = document.getElementById('types');
|
||||
const height = document.getElementById('height');
|
||||
const weight = document.getElementById('weight');
|
||||
const hp = document.getElementById('hp');
|
||||
const attack = document.getElementById('attack');
|
||||
const defense = document.getElementById('defense');
|
||||
const specialAttack = document.getElementById('special-attack');
|
||||
const specialDefense = document.getElementById('special-defense');
|
||||
const speed = document.getElementById('speed');
|
||||
const searchForm = document.getElementById('search-form');
|
||||
const searchInput = document.getElementById('search-input');
|
||||
|
||||
const getPokemon = async () => {
|
||||
const getCreature = async () => {
|
||||
try {
|
||||
const pokemonNameOrId = searchInput.value.toLowerCase();
|
||||
const creatureNameOrId = searchInput.value.toLowerCase();
|
||||
const response = await fetch(
|
||||
`https://pokeapi-proxy.freecodecamp.rocks/api/pokemon/${pokemonNameOrId}`,
|
||||
`https://rpg-creature-api.freecodecamp.rocks/api/creature/${creatureNameOrId}`
|
||||
);
|
||||
const data = await response.json();
|
||||
|
||||
// Set Pokémon info
|
||||
pokemonName.textContent = `${data.name.toUpperCase()}`;
|
||||
pokemonID.textContent = `#${data.id}`;
|
||||
// Set Creature info
|
||||
creatureName.textContent = `${data.name.toUpperCase()}`;
|
||||
creatureID.textContent = `#${data.id}`;
|
||||
weight.textContent = `Weight: ${data.weight}`;
|
||||
height.textContent = `Height: ${data.height}`;
|
||||
spriteContainer.innerHTML = `
|
||||
<img id="sprite" src="${data.sprites.front_default}" alt="${data.name} front default sprite">
|
||||
`;
|
||||
specialName.textContent = data.special.name;
|
||||
specialDescription.textContent = data.special.description;
|
||||
|
||||
// Set stats
|
||||
hp.textContent = data.stats[0].base_stat;
|
||||
@@ -841,37 +808,34 @@ const getPokemon = async () => {
|
||||
|
||||
// Set types
|
||||
types.innerHTML = data.types
|
||||
.map(
|
||||
(obj) => `<span class="type ${obj.type.name}">${obj.type.name}</span>`,
|
||||
)
|
||||
.join("");
|
||||
.map(obj => `<span class="type ${obj.name}">${obj.name}</span>`)
|
||||
.join('');
|
||||
} catch (err) {
|
||||
resetDisplay();
|
||||
alert("Pokémon not found");
|
||||
console.log(`Pokémon not found: ${err}`);
|
||||
alert('Creature not found');
|
||||
console.log(`Creature not found: ${err}`);
|
||||
}
|
||||
};
|
||||
|
||||
const resetDisplay = () => {
|
||||
const sprite = document.getElementById("sprite");
|
||||
if (sprite) sprite.remove();
|
||||
|
||||
// reset stats
|
||||
pokemonName.textContent = "";
|
||||
pokemonID.textContent = "";
|
||||
types.innerHTML = "";
|
||||
height.textContent = "";
|
||||
weight.textContent = "";
|
||||
hp.textContent = "";
|
||||
attack.textContent = "";
|
||||
defense.textContent = "";
|
||||
specialAttack.textContent = "";
|
||||
specialDefense.textContent = "";
|
||||
speed.textContent = "";
|
||||
creatureName.textContent = '';
|
||||
creatureID.textContent = '';
|
||||
height.textContent = '';
|
||||
weight.textContent = '';
|
||||
types.innerHTML = '';
|
||||
specialName.innerHTML = '';
|
||||
specialDescription.innerHTML = '';
|
||||
hp.textContent = '';
|
||||
attack.textContent = '';
|
||||
defense.textContent = '';
|
||||
specialAttack.textContent = '';
|
||||
specialDefense.textContent = '';
|
||||
speed.textContent = '';
|
||||
};
|
||||
|
||||
searchForm.addEventListener("submit", (e) => {
|
||||
searchForm.addEventListener('submit', e => {
|
||||
e.preventDefault();
|
||||
getPokemon();
|
||||
getCreature();
|
||||
});
|
||||
```
|
||||
Reference in New Issue
Block a user