From 0a4a4a471b720699c8fcc943ed4e170e7fef5b96 Mon Sep 17 00:00:00 2001 From: Alberto Date: Tue, 8 Sep 2026 12:31:45 +0200 Subject: [PATCH 1/3] feat: add strict typing and display general pokemon info --- src/components/PokemonCard/PokemonCard.tsx | 46 +++++++++++++++++++--- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/src/components/PokemonCard/PokemonCard.tsx b/src/components/PokemonCard/PokemonCard.tsx index 2ae336d..84489fa 100644 --- a/src/components/PokemonCard/PokemonCard.tsx +++ b/src/components/PokemonCard/PokemonCard.tsx @@ -1,7 +1,17 @@ import "./PokemonCard.css"; +type PokemonData = { + name: string; + sprites: { front_default: string }; + stats: Array<{ base_stat: number; stat: { name: string } }>; + height: number; + weight: number; + types: Array<{ type: { name: string } }>; + abilities: Array<{ ability: { name: string }; is_hidden: boolean }>; +}; + type PokemonCardProps = { - pokemon: any; + pokemon: PokemonData; }; export function PokemonCard({ pokemon }: PokemonCardProps) { @@ -20,12 +30,38 @@ export function PokemonCard({ pokemon }: PokemonCardProps) { let total = 0; return ( -
+

{displayName}

- - + {`${displayName} + +
+
+ Type: +
+ {pokemon.types.map((t) => ( + + {t.type.name} + + ))} +
+
+
+ Height: {pokemon.height / 10} m +
+
+ Weight: {pokemon.weight / 10} kg +
+
+ Abilities:{" "} + {pokemon.abilities + .map((a) => a.ability.name + (a.is_hidden ? " (Hidden)" : "")) + .join(", ")} +
+
+ +
- {pokemon.stats.map((stat: any) => { + {pokemon.stats.map((stat) => { const base = stat.base_stat; total += base; From 70ccc4ac1ff53c66c0b335af7df521b626a17612 Mon Sep 17 00:00:00 2001 From: Alberto Date: Tue, 8 Sep 2026 12:33:03 +0200 Subject: [PATCH 2/3] style: scope component css and format general info box --- src/components/PokemonCard/PokemonCard.css | 68 ++++++++++++++++------ 1 file changed, 51 insertions(+), 17 deletions(-) diff --git a/src/components/PokemonCard/PokemonCard.css b/src/components/PokemonCard/PokemonCard.css index a803b44..4a8a922 100644 --- a/src/components/PokemonCard/PokemonCard.css +++ b/src/components/PokemonCard/PokemonCard.css @@ -1,4 +1,4 @@ -div { +.pokemon-card { display: flex; flex-direction: column; align-items: center; @@ -9,38 +9,72 @@ div { background: #fff; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + margin: 0 auto; + max-width: 500px; } -h1 { +.pokemon-card h1 { text-align: center; margin-bottom: 10px; } -img { +.pokemon-card img { display: block; margin: 0 auto 20px auto; width: 120px; height: 120px; } -table { +.general-info { + width: 100%; + background: #f8f9fa; + padding: 16px; + border-radius: 8px; + margin-bottom: 24px; + display: flex; + flex-direction: column; + gap: 8px; +} + +.info-row { + display: flex; + align-items: center; + gap: 8px; + font-size: 14px; +} + +.type-badges { + display: flex; + gap: 6px; +} + +.type-badge { + background: #333; + color: white; + padding: 2px 8px; + border-radius: 12px; + font-size: 12px; + text-transform: capitalize; +} + +.stats-table { display: grid; grid-template-columns: auto auto 1fr auto auto; width: 100%; border-collapse: collapse; } -tbody, -tfoot, -tr { +.stats-table tbody, +.stats-table tfoot, +.stats-table tr { display: contents; } -tr { +.stats-table tr { border-bottom: 1px solid #eee; } -th { +.stats-table th { padding: 8px 4px; font-weight: normal; color: #666; @@ -52,13 +86,13 @@ th { white-space: nowrap; } -th.cell-num:nth-child(1) { +.stats-table th.cell-num:nth-child(1) { text-align: left; color: #666; font-weight: 500; } -th.cell-num:nth-child(2) { +.stats-table th.cell-num:nth-child(2) { font-weight: bold; color: #333; } @@ -79,31 +113,31 @@ th.cell-num:nth-child(2) { padding: 6px; } -tfoot tr { +.stats-table tfoot tr { border-bottom: none; } -tfoot th { +.stats-table tfoot th { color: #333; font-weight: bold !important; } -tfoot th:nth-child(1) { +.stats-table tfoot th:nth-child(1) { grid-column: 1; text-align: left; } -tfoot th:nth-child(2) { +.stats-table tfoot th:nth-child(2) { grid-column: 2; text-align: right; } -tfoot th:nth-child(3) { +.stats-table tfoot th:nth-child(3) { grid-column: 4; text-align: right; } -tfoot th:nth-child(4) { +.stats-table tfoot th:nth-child(4) { grid-column: 5; text-align: right; } From 37b02c342b6c17c0445706575bf0cacf04c0bf2b Mon Sep 17 00:00:00 2001 From: Alberto Date: Tue, 8 Sep 2026 12:33:09 +0200 Subject: [PATCH 3/3] test: add unit and snapshot tests for pokemon card --- .../PokemonCard/PokemonCard.test.tsx | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/components/PokemonCard/PokemonCard.test.tsx b/src/components/PokemonCard/PokemonCard.test.tsx index bd2b506..b5a816f 100644 --- a/src/components/PokemonCard/PokemonCard.test.tsx +++ b/src/components/PokemonCard/PokemonCard.test.tsx @@ -1,5 +1,5 @@ import { render, screen } from "@testing-library/react"; -import { expect, test } from "vitest"; +import { describe, expect, test } from "vitest"; import { PokemonCard } from "./PokemonCard"; const mockPokemon = { @@ -8,6 +8,10 @@ const mockPokemon = { front_default: "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/25.png", }, + height: 4, + weight: 60, + types: [{ type: { name: "electric" } }], + abilities: [{ ability: { name: "static" }, is_hidden: false }], stats: [ { base_stat: 35, stat: { name: "hp" } }, { base_stat: 55, stat: { name: "attack" } }, @@ -18,7 +22,19 @@ const mockPokemon = { ], }; -test("renders pokemon name", () => { - render(); - expect(screen.getByText("Pikachu")).toBeInTheDocument(); +describe("PokemonCard Component", () => { + test("renders pokemon name and general information", () => { + render(); + + expect(screen.getByText("Pikachu")).toBeDefined(); + expect(screen.getByText("electric")).toBeDefined(); + expect(screen.getByText("static")).toBeDefined(); + expect(screen.getByText("0.4 m")).toBeDefined(); + expect(screen.getByText("6 kg")).toBeDefined(); + }); + + test("matches snapshot", () => { + const { asFragment } = render(); + expect(asFragment()).toMatchSnapshot(); + }); });