From d9bce07359d5ddfe15889a231bfd44adb6f262b9 Mon Sep 17 00:00:00 2001 From: Alberto Date: Tue, 8 Sep 2026 13:53:53 +0200 Subject: [PATCH] feat: create PokemonList component with styles and tests --- src/components/PokemonList/PokemonList.css | 65 +++++++++++++++++++ .../PokemonList/PokemonList.test.tsx | 44 +++++++++++++ src/components/PokemonList/PokemonList.tsx | 54 +++++++++++++++ 3 files changed, 163 insertions(+) create mode 100644 src/components/PokemonList/PokemonList.css create mode 100644 src/components/PokemonList/PokemonList.test.tsx create mode 100644 src/components/PokemonList/PokemonList.tsx diff --git a/src/components/PokemonList/PokemonList.css b/src/components/PokemonList/PokemonList.css new file mode 100644 index 0000000..691a86d --- /dev/null +++ b/src/components/PokemonList/PokemonList.css @@ -0,0 +1,65 @@ +.pokemon-list { + display: flex; + flex-direction: column; + gap: 8px; + width: 100%; + max-width: 300px; + max-height: 500px; + overflow-y: auto; + background: #f8f9fa; + padding: 12px; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); +} + +.pokemon-list-empty { + padding: 24px; + text-align: center; + color: #666; + font-family: sans-serif; +} + +.pokemon-list-item { + display: flex; + align-items: center; + gap: 12px; + padding: 8px 12px; + background: #fff; + border: 1px solid #ddd; + border-radius: 6px; + cursor: pointer; + transition: all 0.2s ease; + text-align: left; + font-family: sans-serif; +} + +.pokemon-list-item:hover { + background: #f0f0f0; + transform: translateY(-1px); +} + +.pokemon-list-item.selected { + border-color: #007bff; + background: #e6f2ff; + box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25); +} + +.pokemon-list-item img { + width: 40px; + height: 40px; + object-fit: contain; +} + +.pokemon-id { + font-size: 12px; + color: #888; + font-weight: bold; + min-width: 35px; +} + +.pokemon-name { + font-size: 14px; + font-weight: 500; + color: #333; + text-transform: capitalize; +} diff --git a/src/components/PokemonList/PokemonList.test.tsx b/src/components/PokemonList/PokemonList.test.tsx new file mode 100644 index 0000000..a6dacd9 --- /dev/null +++ b/src/components/PokemonList/PokemonList.test.tsx @@ -0,0 +1,44 @@ +import { describe, expect, test, vi } from "vitest"; +import { render, screen, fireEvent } from "@testing-library/react"; +import { PokemonList } from "./PokemonList"; + +const mockPokemons = [ + { id: 25, name: "pikachu", sprite: "pikachu.png" }, + { id: 4, name: "charmander", sprite: "charmander.png" }, + { id: 1, name: "bulbasaur", sprite: "bulbasaur.png" }, +]; + +describe("PokemonList Component", () => { + test("renders sorted list of pokemon", () => { + render( {}} />); + + const items = screen.getAllByRole("button"); + expect(items).toHaveLength(3); + + // Verifies sorting by ID (1, 4, 25) + expect(items[0].textContent).toContain("bulbasaur"); + expect(items[1].textContent).toContain("charmander"); + expect(items[2].textContent).toContain("pikachu"); + }); + + test("calls onSelect with correct ID when clicked", () => { + const handleSelect = vi.fn(); + render(); + + const pikachuButton = screen.getByText("pikachu"); + fireEvent.click(pikachuButton); + + expect(handleSelect).toHaveBeenCalledWith(25); + }); + + test("matches snapshot", () => { + const { asFragment } = render( + {}} + selectedId={4} + />, + ); + expect(asFragment()).toMatchSnapshot(); + }); +}); diff --git a/src/components/PokemonList/PokemonList.tsx b/src/components/PokemonList/PokemonList.tsx new file mode 100644 index 0000000..7de40f2 --- /dev/null +++ b/src/components/PokemonList/PokemonList.tsx @@ -0,0 +1,54 @@ +import "./PokemonList.css"; + +export type ListPokemon = { + id: number; + name: string; + sprite: string; +}; + +type PokemonListProps = { + pokemons: ListPokemon[]; + onSelect: (id: number) => void; + selectedId?: number; +}; + +export function PokemonList({ + pokemons, + onSelect, + selectedId, +}: PokemonListProps) { + // Ensure the list is always sorted by ID as required + const sortedPokemons = [...pokemons].sort((a, b) => a.id - b.id); + + if (sortedPokemons.length === 0) { + return ( +
No Pokémon available to display.
+ ); + } + + return ( +
+ {sortedPokemons.map((pokemon) => { + const isSelected = pokemon.id === selectedId; + return ( + + ); + })} +
+ ); +}