diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e1e67ba..5135306 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -30,7 +30,7 @@ on:
- main
env:
- VERSION_NUMBER: 'v1.6.0'
+ VERSION_NUMBER: 'v1.6.1'
DOCKERHUB_REGISTRY_NAME: 'digitalghostdev/poke-cli'
AWS_REGION: 'us-west-2'
diff --git a/.goreleaser.yml b/.goreleaser.yml
index 46cfe4d..380e571 100644
--- a/.goreleaser.yml
+++ b/.goreleaser.yml
@@ -14,7 +14,7 @@ builds:
- windows
- darwin
ldflags:
- - -s -w -X main.version=v1.6.0
+ - -s -w -X main.version=v1.6.1
archives:
- formats: [ 'zip' ]
@@ -39,15 +39,15 @@ changelog:
homebrew_casks:
- name: poke-cli
+ description: "A hybrid CLI/TUI tool written in Go for viewing Pokémon data from the terminal!"
+ homepage: "https://docs.poke-cli.com/"
+ license: MIT
conflicts:
- formula: poke-cli
repository:
owner: digitalghost-dev
name: homebrew-tap
token: "{{.Env.GITHUB_TOKEN}}"
- homepage: "https://docs.poke-cli.com/"
- description: "A hybrid CLI/TUI tool written in Go for viewing Pokémon data from the terminal!"
- license: "Apache License 2.0"
hooks:
post:
install: |
@@ -55,21 +55,12 @@ homebrew_casks:
system_command "/usr/bin/xattr", args: ["-dr", "com.apple.quarantine", "#{staged_path}/poke-cli"]
end
-winget:
+scoops:
- name: poke-cli
- publisher: digitalghost-dev
- license: MIT
+ description: "A hybrid CLI/TUI tool written in Go for viewing Pokémon data from the terminal!"
homepage: "https://docs.poke-cli.com/"
- short_description: "Pokémon CLI/TUI terminal tool."
+ license: MIT
repository:
owner: digitalghost-dev
- name: winget-pkgs
- token: "{{.Env.GITHUB_TOKEN}}"
- branch: "poke-cli-{{.Version}}"
- pull_request:
- enabled: true
- base:
- owner: microsoft
- name: winget-pkgs
- branch: master
- description: "A hybrid CLI/TUI tool written in Go for viewing Pokémon data from the terminal!"
\ No newline at end of file
+ name: scoop-bucket
+ token: "{{ .Env.GITHUB_TOKEN }}"
diff --git a/Dockerfile b/Dockerfile
index 9ea0df2..c86729c 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -8,7 +8,7 @@ RUN go mod download
COPY . .
-RUN go build -ldflags "-X main.version=v1.6.0" -o poke-cli .
+RUN go build -ldflags "-X main.version=v1.6.1" -o poke-cli .
# build 2
FROM --platform=$BUILDPLATFORM alpine:3.22
diff --git a/README.md b/README.md
index 0ea351b..65e11bf 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
Pokémon CLI
-
+
@@ -30,7 +30,7 @@ View future plans in the [Roadmap](#roadmap) section.
## Installation
* [Homebrew](#homebrew)
-* [Winget](#winget)
+* [Scoop](#scoop)
* [Linux Packages](#linux-packages)
* [Docker Image](#docker-image)
* [Binary](#binary)
@@ -42,19 +42,24 @@ View future plans in the [Roadmap](#roadmap) section.
```bash
brew install --cask digitalghost-dev/tap/poke-cli
````
-2. Verify install:
+2. Verify installation:
```bash
poke-cli -v
```
-### Winget
-1. Install the package:
+### Scoop
+1. Add the bucket:
```powershell
- winget install poke-cli
+ scoop bucket add digitalghost https://github.com/digitalghost-dev/scoop-bucket.git
```
-2. Verify install:
- ```bash
+2. Install poke-cli:
+ ```powershell
+ scoop install poke-cli
+ ```
+
+3. Verify installation:
+ ```powershell
poke-cli -v
```
@@ -86,11 +91,11 @@ Cloudsmith is a fully cloud-based service that lets you easily create, store, an
3. Choose how to interact with the container:
* Run a single command and exit:
```bash
- docker run --rm -it digitalghostdev/poke-cli:v1.6.0 [subcommand] flag]
+ docker run --rm -it digitalghostdev/poke-cli:v1.6.1 [subcommand] flag]
```
* Enter the container and use its shell:
```bash
- docker run --rm -it --name poke-cli --entrypoint /bin/sh digitalghostdev/poke-cli:v1.6.0 -c "cd /app && exec sh"
+ docker run --rm -it --name poke-cli --entrypoint /bin/sh digitalghostdev/poke-cli:v1.6.1 -c "cd /app && exec sh"
# placed into the /app directory, run the program with './poke-cli'
# example: ./poke-cli ability swift-swim
```
diff --git a/card_data/infrastructure/aws/rds/provider.tf b/card_data/infrastructure/aws/rds/provider.tf
index eee0069..56b4453 100755
--- a/card_data/infrastructure/aws/rds/provider.tf
+++ b/card_data/infrastructure/aws/rds/provider.tf
@@ -1,12 +1,20 @@
provider "aws" {
- profile = "terraform-user"
- region = "us-west-2"
+ region = "us-west-2"
}
terraform {
- required_providers {
- aws = {
- version = "~> 6.7.0"
- }
+ cloud {
+ organization = "digitalghost-dev"
+
+ workspaces {
+ project = "poke-cli"
+ name = "poke-cli"
+ }
}
-}
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ version = "~> 6.7.0"
+ }
+ }
+}
\ No newline at end of file
diff --git a/card_data/pipelines/definitions.py b/card_data/pipelines/definitions.py
new file mode 100644
index 0000000..6941003
--- /dev/null
+++ b/card_data/pipelines/definitions.py
@@ -0,0 +1,7 @@
+from pathlib import Path
+from dagster import definitions, load_from_defs_folder
+
+
+@definitions
+def defs():
+ return load_from_defs_folder(project_root=Path(__file__).parent.parent)
\ No newline at end of file
diff --git a/card_data/pipelines/defs/extract/extract_data.py b/card_data/pipelines/defs/extract/extract_data.py
new file mode 100644
index 0000000..f8c9e94
--- /dev/null
+++ b/card_data/pipelines/defs/extract/extract_data.py
@@ -0,0 +1,177 @@
+import time
+import json
+
+import dagster as dg
+import polars as pl
+
+from pydantic import BaseModel, HttpUrl, ValidationError
+from typing import Optional
+from termcolor import colored
+
+import requests
+
+class Series(BaseModel):
+ id: str
+ name: str
+ logo: Optional[HttpUrl] = None
+
+
+class Set(BaseModel):
+ series_id: str
+ set_id: str
+ set_name: str
+ official_card_count: int | None
+ total_card_count: int | None
+ logo: Optional[str] = None
+ symbol: Optional[str] = None
+
+
+@dg.asset(kinds={"API", "Polars", "Pydantic"})
+def extract_series_data() -> pl.DataFrame:
+ url: str = "https://api.tcgdex.net/v2/en/series"
+ data = requests.get(url).json()
+
+ # Pydantic validation
+ try:
+ validated: list[Series] = [Series(**item) for item in data]
+ print(
+ colored(" ✓", "green"), "Pydantic validation passed for all series entries."
+ )
+ except ValidationError as e:
+ print(colored(" ✖", "red"), "Pydantic validation failed.")
+ print(e)
+ raise
+
+ filtered = [s.model_dump(mode="json") for s in validated if s.id in ["swsh", "sv"]]
+ return pl.DataFrame(filtered)
+
+
+@dg.asset(kinds={"API", "Polars", "Pydantic"})
+def extract_set_data() -> pl.DataFrame:
+ url_list = [
+ "https://api.tcgdex.net/v2/en/series/swsh",
+ "https://api.tcgdex.net/v2/en/series/sv"
+ ]
+
+ flat: list[dict] = []
+
+ for url in url_list:
+ data = requests.get(url).json()
+ series_id = data.get("id")
+
+ for s in data.get("sets", []):
+ entry = {
+ "series_id": series_id,
+ "set_id": s.get("id"),
+ "set_name": s.get("name"),
+ "official_card_count": s.get("cardCount", {}).get("official"),
+ "total_card_count": s.get("cardCount", {}).get("total"),
+ "logo": s.get("logo"),
+ "symbol": s.get("symbol")
+ }
+ flat.append(entry)
+
+ # Pydantic validation
+ try:
+ validated: list[Set] = [Set(**item) for item in flat]
+ print(
+ colored(" ✓", "green"),
+ "Pydantic validation passed for all set entries."
+ )
+ except ValidationError as e:
+ print(colored(" ✖", "red"), "Pydantic validation failed.")
+ print(e)
+ raise
+
+ return pl.DataFrame([s.model_dump(mode="json") for s in validated])
+
+
+@dg.asset(kinds={"API"})
+def extract_card_url_from_set() -> list:
+ urls = [
+ "https://api.tcgdex.net/v2/en/sets/sv01",
+ "https://api.tcgdex.net/v2/en/sets/sv02",
+ ]
+
+ all_card_urls = [] # Initialize empty list to collect all URLs
+
+ for url in urls:
+ try:
+ r = requests.get(url)
+ r.raise_for_status()
+
+ data = r.json()["cards"]
+
+ set_card_urls = [f"https://api.tcgdex.net/v2/en/cards/{card['id']}" for card in data]
+ all_card_urls.extend(set_card_urls) # Add all URLs from this set
+
+ time.sleep(0.1)
+
+ except requests.RequestException as e:
+ print(f"Failed to fetch set {url}: {e}")
+
+ return all_card_urls
+
+
+@dg.asset(deps=[extract_card_url_from_set], kinds={"API"})
+def extract_card_info() -> list:
+ card_url_list = extract_card_url_from_set()
+ cards_list = []
+
+ for url in card_url_list:
+ try:
+ r = requests.get(url)
+ r.raise_for_status()
+ data = r.json()
+ cards_list.append(data)
+ time.sleep(0.1)
+ except requests.RequestException as e:
+ print(f"Failed to fetch {url}: {e}")
+
+ return cards_list
+
+
+@dg.asset(deps=[extract_card_info], kinds={"Polars"})
+def create_card_dataframe() -> pl.DataFrame:
+ cards_list = extract_card_info()
+
+ all_flat_cards = []
+
+ for card in cards_list:
+ flat = {}
+
+ # Copy top-level scalar values
+ scalar_keys = ['category', 'hp', 'id', 'illustrator', 'image', 'localId',
+ 'name', 'rarity', 'regulationMark', 'retreat', 'stage']
+ for key in scalar_keys:
+ flat[key] = card.get(key)
+
+ # Flatten nested dicts with prefixes
+ for key, value in card.get("legal", {}).items():
+ flat[f"legal_{key}"] = value
+
+ for key, value in card.get("set", {}).items():
+ if isinstance(value, dict):
+ for sub_key, sub_val in value.items():
+ flat[f"set_{key}_{sub_key}"] = sub_val
+ else:
+ flat[f"set_{key}"] = value
+
+ # Flatten types (list of strings)
+ flat["types"] = ", ".join(card.get("types", []))
+
+ flat["attacks_json"] = json.dumps(card.get("attacks", []), ensure_ascii=False)
+
+ attacks = card.get("attacks", [])
+ for i, atk in enumerate(attacks):
+ prefix = f"attack_{i+1}"
+ flat[f"{prefix}_name"] = atk.get("name")
+ flat[f"{prefix}_damage"] = atk.get("damage")
+ flat[f"{prefix}_effect"] = atk.get("effect")
+ flat[f"{prefix}_cost"] = ", ".join(atk.get("cost", []))
+
+ all_flat_cards.append(flat)
+
+ df = pl.DataFrame(all_flat_cards)
+
+ return df
diff --git a/cmd/pokemon/pokemon.go b/cmd/pokemon/pokemon.go
index 0dece62..061e480 100644
--- a/cmd/pokemon/pokemon.go
+++ b/cmd/pokemon/pokemon.go
@@ -75,22 +75,26 @@ func PokemonCommand() (string, error) {
output.WriteString(err.Error())
return output.String(), err
}
- capitalizedString := cases.Title(language.English).String(strings.ReplaceAll(pokemonName, "-", " "))
- // Weight calculation
- weightKilograms := float64(pokemonStruct.Weight) / 10
- weightPounds := float64(weightKilograms) * 2.20462
+ pokemonSpeciesStruct, err := connections.PokemonSpeciesApiCall("pokemon-species", pokemonName, connections.APIURL)
+ if err != nil {
+ output.WriteString(err.Error())
+ return output.String(), err
+ }
+
+ capitalizedString := cases.Title(language.English).String(strings.ReplaceAll(pokemonName, "-", " "))
- // Height calculation
- heightMeters := float64(pokemonStruct.Height) / 10
- heightFeet := heightMeters * 3.28084
- feet := int(heightFeet)
- inches := int(math.Round((heightFeet - float64(feet)) * 12)) // Use math.Round to avoid truncation
+ entry := func(w io.Writer) {
+ for _, entry := range pokemonSpeciesStruct.FlavorTextEntries {
+ if entry.Language.Name == "en" && (entry.Version.Name == "x" || entry.Version.Name == "shield" || entry.Version.Name == "scarlet") {
+ flavorText := strings.ReplaceAll(entry.FlavorText, "\n", " ")
+ flavorText = strings.Join(strings.Fields(flavorText), " ")
- // Adjust for rounding to 12 inches (carry over to the next foot)
- if inches == 12 {
- feet++
- inches = 0
+ wrapped := utils.WrapText(flavorText, 60)
+ fmt.Fprintln(w, wrapped)
+ return
+ }
+ }
}
typing := func(w io.Writer) {
@@ -117,15 +121,55 @@ func PokemonCommand() (string, error) {
fmt.Fprintln(w, joinedTypes)
}
- var typeOutput bytes.Buffer
+ metrics := func(w io.Writer) {
+ // Weight calculation
+ weightKilograms := float64(pokemonStruct.Weight) / 10
+ weightPounds := float64(weightKilograms) * 2.20462
+
+ // Height calculation
+ heightMeters := float64(pokemonStruct.Height) / 10
+ heightFeet := heightMeters * 3.28084
+ feet := int(heightFeet)
+ inches := int(math.Round((heightFeet - float64(feet)) * 12)) // Use math.Round to avoid truncation
+
+ // Adjust for rounding to 12 inches (carry over to the next foot)
+ if inches == 12 {
+ feet++
+ inches = 0
+ }
+
+ fmt.Fprintf(w, "\n%s National Pokédex #: %d\n%s Weight: %.1fkg (%.1f lbs)\n%s Height: %.1fm (%d′%02d″)\n",
+ styling.ColoredBullet, pokemonStruct.ID,
+ styling.ColoredBullet, weightKilograms, weightPounds,
+ styling.ColoredBullet, heightMeters, feet, inches)
+ }
+
+ species := func(w io.Writer) {
+ if pokemonSpeciesStruct.EvolvesFromSpecies.Name != "" {
+ evolvesFrom := pokemonSpeciesStruct.EvolvesFromSpecies.Name
+
+ capitalizedPokemonName := cases.Title(language.English).String(strings.ReplaceAll(evolvesFrom, "-", " "))
+ fmt.Fprintf(w, "%s %s %s", styling.ColoredBullet, "Evolves from:", capitalizedPokemonName)
+ } else {
+ fmt.Fprintf(w, "%s %s", styling.ColoredBullet, "Basic Pokémon")
+ }
+ }
+
+ var (
+ entryOutput bytes.Buffer
+ typeOutput bytes.Buffer
+ metricsOutput bytes.Buffer
+ speciesOutput bytes.Buffer
+ )
+
+ entry(&entryOutput)
typing(&typeOutput)
+ metrics(&metricsOutput)
+ species(&speciesOutput)
output.WriteString(fmt.Sprintf(
- "Your selected Pokémon: %s\n%s\n%s National Pokédex #: %d\n%s Weight: %.1fkg (%.1f lbs)\n%s Height: %.1fm (%d′%02d″)\n",
- capitalizedString, typeOutput.String(),
- styling.ColoredBullet, pokemonStruct.ID,
- styling.ColoredBullet, weightKilograms, weightPounds,
- styling.ColoredBullet, heightMeters, feet, inches,
+ "Your selected Pokémon: %s\n%s%s%s%s\n",
+ capitalizedString, entryOutput.String(), typeOutput.String(), metricsOutput.String(), speciesOutput.String(),
))
if *imageFlag != "" || *shortImageFlag != "" {
@@ -142,38 +186,22 @@ func PokemonCommand() (string, error) {
}
}
- if *abilitiesFlag || *shortAbilitiesFlag {
- if err := flags.AbilitiesFlag(&output, endpoint, pokemonName); err != nil {
- output.WriteString(fmt.Sprintf("error parsing flags: %v\n", err))
- return "", fmt.Errorf("error parsing flags: %w", err)
- }
- }
-
- if *defenseFlag || *shortDefenseFlag {
- if err := flags.DefenseFlag(&output, endpoint, pokemonName); err != nil {
- output.WriteString(fmt.Sprintf("error parsing flags: %v\n", err))
- return "", fmt.Errorf("error parsing flags: %w", err)
- }
- }
-
- if *moveFlag || *shortMoveFlag {
- if err := flags.MovesFlag(&output, endpoint, pokemonName); err != nil {
- output.WriteString(fmt.Sprintf("error parsing flags: %v\n", err))
- return "", fmt.Errorf("error parsing flags: %w", err)
- }
+ flagChecks := []struct {
+ condition bool
+ flagFunc func(io.Writer, string, string) error
+ }{
+ {*abilitiesFlag || *shortAbilitiesFlag, flags.AbilitiesFlag},
+ {*defenseFlag || *shortDefenseFlag, flags.DefenseFlag},
+ {*moveFlag || *shortMoveFlag, flags.MovesFlag},
+ {*typesFlag || *shortTypesFlag, flags.TypesFlag},
+ {*statsFlag || *shortStatsFlag, flags.StatsFlag},
}
- if *typesFlag || *shortTypesFlag {
- if err := flags.TypesFlag(&output, endpoint, pokemonName); err != nil {
- output.WriteString(fmt.Sprintf("error parsing flags: %v\n", err))
- return "", fmt.Errorf("error parsing flags: %w", err)
- }
- }
-
- if *statsFlag || *shortStatsFlag {
- if err := flags.StatsFlag(&output, endpoint, pokemonName); err != nil {
- output.WriteString(fmt.Sprintf("error parsing flags: %v\n", err))
- return "", fmt.Errorf("error parsing flags: %w", err)
+ for _, check := range flagChecks {
+ if check.condition {
+ if err := check.flagFunc(&output, endpoint, pokemonName); err != nil {
+ return utils.HandleFlagError(&output, err)
+ }
}
}
diff --git a/cmd/utils/output.go b/cmd/utils/output.go
index e942329..e658261 100644
--- a/cmd/utils/output.go
+++ b/cmd/utils/output.go
@@ -3,6 +3,7 @@ package utils
import (
"fmt"
"os"
+ "strings"
)
// HandleCommandOutput takes a function that returns (string, error) and wraps it in a no-argument
@@ -19,3 +20,37 @@ func HandleCommandOutput(fn func() (string, error)) func() int {
return 0
}
}
+
+func HandleFlagError(output *strings.Builder, err error) (string, error) {
+ fmt.Fprintf(output, "error parsing flags: %v\n", err)
+ return "", fmt.Errorf("error parsing flags: %w", err)
+}
+
+func WrapText(text string, width int) string {
+ words := strings.Fields(text)
+ if len(words) == 0 {
+ return text
+ }
+
+ var result strings.Builder
+ lineLength := 0
+
+ for _, word := range words {
+ wordLen := len(word)
+
+ if lineLength > 0 && lineLength+1+wordLen > width {
+ result.WriteString("\n")
+ lineLength = 0
+ }
+
+ if lineLength > 0 {
+ result.WriteString(" ")
+ lineLength++
+ }
+
+ result.WriteString(word)
+ lineLength += wordLen
+ }
+
+ return result.String()
+}
diff --git a/cmd/utils/output_test.go b/cmd/utils/output_test.go
index 2454850..cc2c865 100644
--- a/cmd/utils/output_test.go
+++ b/cmd/utils/output_test.go
@@ -4,6 +4,7 @@ import (
"errors"
"io"
"os"
+ "strings"
"testing"
)
@@ -53,3 +54,85 @@ func TestHandleCommandOutput_Error(t *testing.T) {
t.Errorf("expected 'something failed\\n', got %q", output)
}
}
+
+func TestHandleFlagError_WithError(t *testing.T) {
+ var b strings.Builder
+ msg, err := HandleFlagError(&b, errors.New("bad flag"))
+
+ if got := b.String(); got != "error parsing flags: bad flag\n" {
+ t.Fatalf("builder content mismatch: got %q", got)
+ }
+ if msg != "" {
+ t.Fatalf("expected empty message, got %q", msg)
+ }
+ if err == nil {
+ t.Fatalf("expected non-nil error")
+ }
+ if err.Error() != "error parsing flags: bad flag" {
+ t.Fatalf("unexpected error message: %q", err.Error())
+ }
+}
+
+func TestHandleFlagError_NilError(t *testing.T) {
+ var b strings.Builder
+ msg, err := HandleFlagError(&b, nil)
+
+ if got := b.String(); got != "error parsing flags: \n" {
+ t.Fatalf("builder content mismatch for nil error: got %q", got)
+ }
+ if msg != "" {
+ t.Fatalf("expected empty message, got %q", msg)
+ }
+ if err == nil {
+ t.Fatalf("expected non-nil error when wrapping nil")
+ }
+ // Document current behavior of fmt.Errorf with %w and nil
+ if err.Error() != "error parsing flags: %!w()" {
+ t.Fatalf("unexpected error message for nil wrap: %q", err.Error())
+ }
+}
+
+func TestWrapText_EmptyString(t *testing.T) {
+ if got := WrapText("", 10); got != "" {
+ t.Fatalf("expected empty string, got %q", got)
+ }
+}
+
+func TestWrapText_OnlySpaces(t *testing.T) {
+ if got := WrapText(" ", 10); got != " " {
+ t.Fatalf("expected to preserve spaces, got %q", got)
+ }
+}
+
+func TestWrapText_NoWrap(t *testing.T) {
+ if got := WrapText("hello world", 20); got != "hello world" {
+ t.Fatalf("expected 'hello world', got %q", got)
+ }
+}
+
+func TestWrapText_CollapseSpaces(t *testing.T) {
+ if got := WrapText("hello world", 20); got != "hello world" {
+ t.Fatalf("expected collapsed spaces, got %q", got)
+ }
+}
+
+func TestWrapText_WrapAtWidth(t *testing.T) {
+ if got := WrapText("hello world", 10); got != "hello\nworld" {
+ t.Fatalf("expected wrap at width, got %q", got)
+ }
+}
+
+func TestWrapText_LongWord(t *testing.T) {
+ in := "supercalifragilisticexpialidocious"
+ if got := WrapText(in, 10); got != in {
+ t.Fatalf("expected long word unchanged, got %q", got)
+ }
+}
+
+func TestWrapText_MultipleLines(t *testing.T) {
+ in := "one two three four five"
+ expected := "one two\nthree\nfour\nfive"
+ if got := WrapText(in, 7); got != expected {
+ t.Fatalf("expected %q, got %q", expected, got)
+ }
+}
diff --git a/connections/connection.go b/connections/connection.go
index 9d0f1a4..ac6e839 100644
--- a/connections/connection.go
+++ b/connections/connection.go
@@ -5,11 +5,12 @@ import (
"errors"
"flag"
"fmt"
- "github.com/digitalghost-dev/poke-cli/structs"
- "github.com/digitalghost-dev/poke-cli/styling"
"io"
"net/http"
"net/url"
+
+ "github.com/digitalghost-dev/poke-cli/structs"
+ "github.com/digitalghost-dev/poke-cli/styling"
)
const APIURL = "https://pokeapi.co/api/v2/"
@@ -120,6 +121,24 @@ func PokemonApiCall(endpoint string, pokemonName string, baseURL string) (struct
return pokemonStruct, pokemonStruct.Name, nil
}
+// PokemonSpeciesApiCall function for calling the pokemon endpoint of the pokeAPI
+func PokemonSpeciesApiCall(endpoint string, pokemonSpeciesName string, baseURL string) (structs.PokemonSpeciesJSONStruct, error) {
+ fullURL := baseURL + endpoint + "/" + pokemonSpeciesName
+
+ var pokemonSpeciesStruct structs.PokemonSpeciesJSONStruct
+ err := ApiCallSetup(fullURL, &pokemonSpeciesStruct, false)
+
+ if err != nil {
+ errMessage := styling.ErrorBorder.Render(
+ styling.ErrorColor.Render("✖ Error!"),
+ "\nPokémon not found.\n\u2022 Perhaps a typo?\n\u2022 Missing a hyphen instead of a space?",
+ )
+ return structs.PokemonSpeciesJSONStruct{}, fmt.Errorf("%s", errMessage)
+ }
+
+ return pokemonSpeciesStruct, nil
+}
+
// TypesApiCall function for calling the type endpoint of the pokeAPI
func TypesApiCall(endpoint string, typesName string, baseURL string) (structs.TypesJSONStruct, string, int) {
fullURL := baseURL + endpoint + "/" + typesName
diff --git a/connections/connection_test.go b/connections/connection_test.go
index dc4b186..29f1e70 100644
--- a/connections/connection_test.go
+++ b/connections/connection_test.go
@@ -2,12 +2,13 @@ package connections
import (
"encoding/json"
- "github.com/digitalghost-dev/poke-cli/structs"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
"net/http"
"net/http/httptest"
"testing"
+
+ "github.com/digitalghost-dev/poke-cli/structs"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
// TestApiCallSetup - Test for the ApiCallSetup function
@@ -239,3 +240,41 @@ func TestTypesApiCall(t *testing.T) {
assert.Equal(t, "electric", name)
assert.Equal(t, 13, id)
}
+
+func TestPokemonSpeciesApiCall(t *testing.T) {
+ // Successful API call returns expected species data
+ t.Run("Successful API call returns expected species", func(t *testing.T) {
+ expectedSpecies := structs.PokemonSpeciesJSONStruct{
+ Name: "flareon",
+ }
+
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusOK)
+ err := json.NewEncoder(w).Encode(expectedSpecies)
+ assert.NoError(t, err, "Expected no error for encoding response")
+ }))
+ defer ts.Close()
+
+ species, err := PokemonSpeciesApiCall("/pokemon-species", "flareon", ts.URL)
+
+ require.NoError(t, err, "Expected no error on successful API call")
+ assert.Equal(t, expectedSpecies, species, "Expected species struct does not match")
+ })
+
+ // Failed API call returns styled error
+ t.Run("Failed API call returns styled error", func(t *testing.T) {
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ // Simulate API failure (e.g., 404 Not Found)
+ http.Error(w, "Not Found", http.StatusNotFound)
+ }))
+ defer ts.Close()
+
+ species, err := PokemonSpeciesApiCall("/pokemon-species", "non-existent-species", ts.URL)
+
+ require.Error(t, err, "Expected an error for invalid species")
+ assert.Equal(t, structs.PokemonSpeciesJSONStruct{}, species, "Expected empty species struct on error")
+
+ assert.Contains(t, err.Error(), "Pokémon not found", "Expected 'Pokémon not found' in error message")
+ assert.Contains(t, err.Error(), "Perhaps a typo?", "Expected helpful suggestion in error message")
+ })
+}
diff --git a/flags/pokemonflagset.go b/flags/pokemonflagset.go
index 51c93dc..cac01af 100644
--- a/flags/pokemonflagset.go
+++ b/flags/pokemonflagset.go
@@ -330,18 +330,17 @@ func ImageFlag(w io.Writer, endpoint string, pokemonName string, size string) er
}
// Anonymous function to transform the image to a string
- // ToString generates an ASCII representation of the image with color
ToString := func(width int, height int, img image.Image) string {
// Resize the image to the specified width, preserving aspect ratio
img = imaging.Resize(img, width, height, imaging.NearestNeighbor)
b := img.Bounds()
- imageWidth := b.Max.X - 2 // Adjust width to exclude margins
- h := b.Max.Y - 4 // Adjust height to exclude margins
+
+ imageWidth := b.Max.X
+ h := b.Max.Y
str := strings.Builder{}
- // Loop through the image pixels, two rows at a time
- for heightCounter := 2; heightCounter < h; heightCounter += 2 {
- for x := 1; x < imageWidth; x++ {
+ for heightCounter := 0; heightCounter < h-1; heightCounter += 2 {
+ for x := 0; x < imageWidth; x++ {
// Get the color of the current and next row's pixels
c1, _ := styling.MakeColor(img.At(x, heightCounter))
color1 := lipgloss.Color(c1.Hex())
diff --git a/nfpm.yaml b/nfpm.yaml
index 76d0671..d7cf5d6 100644
--- a/nfpm.yaml
+++ b/nfpm.yaml
@@ -1,7 +1,7 @@
name: "poke-cli"
arch: "arm64"
platform: "linux"
-version: "v1.6.0"
+version: "v1.6.1"
section: "default"
version_schema: semver
maintainer: "Christian S"
diff --git a/structs/structs.go b/structs/structs.go
index 6cb083c..14fdaf1 100644
--- a/structs/structs.go
+++ b/structs/structs.go
@@ -143,6 +143,26 @@ type PokemonJSONStruct struct {
} `json:"stats"`
}
+// PokemonSpeciesJSONStruct pokemon-species endpoint from API
+type PokemonSpeciesJSONStruct struct {
+ Name string `json:"name"`
+ EvolvesFromSpecies struct {
+ Name string `json:"name"`
+ URL string `json:"url"`
+ } `json:"evolves_from_species"`
+ FlavorTextEntries []struct {
+ FlavorText string `json:"flavor_text"`
+ Language struct {
+ Name string `json:"name"`
+ URL string `json:"url"`
+ } `json:"language"`
+ Version struct {
+ Name string `json:"name"`
+ URL string `json:"url"`
+ } `json:"version"`
+ } `json:"flavor_text_entries"`
+}
+
// TypesJSONStruct type endpoint from API
type TypesJSONStruct struct {
Name string `json:"name"`
diff --git a/testdata/main_latest_flag.golden b/testdata/main_latest_flag.golden
index b4cf72d..59c536f 100644
--- a/testdata/main_latest_flag.golden
+++ b/testdata/main_latest_flag.golden
@@ -1,6 +1,6 @@
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ Latest available version: ┃
-┃ • v1.5.2 ┃
+┃ • v1.6.0 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
diff --git a/testdata/pokemon_abilities.golden b/testdata/pokemon_abilities.golden
index 797379d..e0d27a1 100644
--- a/testdata/pokemon_abilities.golden
+++ b/testdata/pokemon_abilities.golden
@@ -1,10 +1,13 @@
Your selected Pokémon: Metagross
+With four linked brains, it’s more intelligent than a
+supercomputer, and it uses calculations to analyze foes.
Steel Psychic
• National Pokédex #: 376
• Weight: 550.0kg (1212.5 lbs)
• Height: 1.6m (5′03″)
+• Evolves from: Metang
─────────
Abilities
Ability 1: Clear Body
diff --git a/testdata/pokemon_defense.golden b/testdata/pokemon_defense.golden
index ecbdc92..3564883 100644
--- a/testdata/pokemon_defense.golden
+++ b/testdata/pokemon_defense.golden
@@ -1,10 +1,13 @@
Your selected Pokémon: Dragapult
+Apparently the Dreepy inside Dragapult’s horns eagerly
+look forward to being launched out at Mach speeds.
Dragon Ghost
• National Pokédex #: 887
• Weight: 50.0kg (110.2 lbs)
• Height: 3.0m (9′10″)
+• Evolves from: Drakloak
─────────────
Type Defenses
Immune: Fighting, Normal
diff --git a/testdata/pokemon_defense_ability_immunities.golden b/testdata/pokemon_defense_ability_immunities.golden
index 2cf19a0..1617e68 100644
--- a/testdata/pokemon_defense_ability_immunities.golden
+++ b/testdata/pokemon_defense_ability_immunities.golden
@@ -1,10 +1,13 @@
Your selected Pokémon: Gastrodon
+When its natural enemy attacks, it oozes purple fluid and
+escapes.
Water Ground
• National Pokédex #: 423
• Weight: 29.9kg (65.9 lbs)
• Height: 0.9m (2′11″)
+• Evolves from: Shellos
─────────────
Type Defenses
Immune: Electric
diff --git a/testdata/pokemon_image.golden b/testdata/pokemon_image.golden
index a42575f..192501f 100644
--- a/testdata/pokemon_image.golden
+++ b/testdata/pokemon_image.golden
@@ -1,51 +1,58 @@
Your selected Pokémon: Skeledirge
+The fiery bird changes shape when Skeledirge sings. Rumor
+has it that the bird was born when the fireball on
+Skeledirge’s head gained a soul.
Fire Ghost
• National Pokédex #: 911
• Weight: 326.5kg (719.8 lbs)
• Height: 1.6m (5′03″)
+• Evolves from: Crocalor
─────
Image
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
-▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
+▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
diff --git a/testdata/pokemon_image_flag_non-valid_size.golden b/testdata/pokemon_image_flag_non-valid_size.golden
index e2a70b2..4f24d0f 100644
--- a/testdata/pokemon_image_flag_non-valid_size.golden
+++ b/testdata/pokemon_image_flag_non-valid_size.golden
@@ -1,10 +1,13 @@
Your selected Pokémon: Floatzel
+It floats using its well-developed flotation sac. It assists
+in the rescues of drowning people.
Water
• National Pokédex #: 419
• Weight: 33.5kg (73.9 lbs)
• Height: 1.1m (3′07″)
+• Evolves from: Buizel
─────
Image
╭───────────────────────────╮
diff --git a/testdata/pokemon_moves.golden b/testdata/pokemon_moves.golden
index 25441c1..1388551 100644
--- a/testdata/pokemon_moves.golden
+++ b/testdata/pokemon_moves.golden
@@ -1,28 +1,35 @@
Your selected Pokémon: Greninja
+It creates throwing stars out of compressed water. When it
+spins them and throws them at high speed, these stars can
+split metal in two.
+
+ Water Dark
+
• National Pokédex #: 658
• Weight: 40.0kg (88.2 lbs)
-• Height: 4.9m (4′11″)
+• Height: 1.5m (4′11″)
+• Evolves from: Frogadier
───────────────
Learnable Moves
-┌──────────────────┬────────┬──────────┬────────┬─────┐
-│Name │Level │Type │Accuracy│Power│
-├──────────────────┼────────┼──────────┼────────┼─────┤
-│Water Shuriken │0 │Water │100 │15 │
-│Pound │1 │Normal │100 │40 │
-│Growl │1 │Normal │100 │0 │
-│Water Gun │1 │Water │100 │40 │
-│Quick Attack │1 │Normal │100 │40 │
-│Night Slash │1 │Dark │100 │70 │
-│Role Play │1 │Psychic │0 │0 │
-│Haze │1 │Ice │0 │0 │
-│Lick │10 │Ghost │100 │30 │
-│Water Pulse │14 │Water │100 │60 │
-│Smokescreen │19 │Normal │100 │0 │
-│Shadow Sneak │23 │Ghost │100 │40 │
-│Spikes │28 │Ground │0 │0 │
-│Aerial Ace │33 │Flying │0 │60 │
-│Substitute │42 │Normal │0 │0 │
-│Extrasensory │49 │Psychic │100 │80 │
-│Double Team │56 │Normal │0 │0 │
-│Hydro Pump │68 │Water │80 │110 │
-└──────────────────┴────────┴──────────┴────────┴─────┘
+┌──────────────────┬────────┬──────────┬──────────┬────────┐
+│Name │Level │Type │Accuracy │Power │
+├──────────────────┼────────┼──────────┼──────────┼────────┤
+│Water Shuriken │0 │Water │100 │15 │
+│Role Play │1 │Psychic │0 │0 │
+│Pound │1 │Normal │100 │40 │
+│Water Gun │1 │Water │100 │40 │
+│Quick Attack │1 │Normal │100 │40 │
+│Night Slash │1 │Dark │100 │70 │
+│Growl │1 │Normal │100 │0 │
+│Haze │1 │Ice │0 │0 │
+│Lick │10 │Ghost │100 │30 │
+│Water Pulse │14 │Water │100 │60 │
+│Smokescreen │19 │Normal │100 │0 │
+│Shadow Sneak │23 │Ghost │100 │40 │
+│Spikes │28 │Ground │0 │0 │
+│Aerial Ace │33 │Flying │0 │60 │
+│Substitute │42 │Normal │0 │0 │
+│Extrasensory │49 │Psychic │100 │80 │
+│Double Team │56 │Normal │0 │0 │
+│Hydro Pump │68 │Water │80 │110 │
+└──────────────────┴────────┴──────────┴──────────┴────────┘
diff --git a/testdata/pokemon_no_flags_dual_type.golden b/testdata/pokemon_no_flags_dual_type.golden
index 4c00755..078f0eb 100644
--- a/testdata/pokemon_no_flags_dual_type.golden
+++ b/testdata/pokemon_no_flags_dual_type.golden
@@ -1,7 +1,10 @@
Your selected Pokémon: Victini
+This Pokémon brings victory. It is said that Trainers with
+Victini always win, regardless of the type of encounter.
Psychic Fire
• National Pokédex #: 494
• Weight: 4.0kg (8.8 lbs)
• Height: 0.4m (1′04″)
+• Basic Pokémon
diff --git a/testdata/pokemon_stats.golden b/testdata/pokemon_stats.golden
index 255d44c..86d40dd 100644
--- a/testdata/pokemon_stats.golden
+++ b/testdata/pokemon_stats.golden
@@ -1,10 +1,13 @@
Your selected Pokémon: Toxicroak
+It has a poison sac at its throat. When it croaks, the
+stored poison is churned for greater potency.
Poison Fighting
• National Pokédex #: 454
• Weight: 44.4kg (97.9 lbs)
• Height: 1.3m (4′03″)
+• Evolves from: Croagunk
──────────
Base Stats
HP ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 83
diff --git a/testdata/pokemon_types.golden b/testdata/pokemon_types.golden
deleted file mode 100644
index 52306ce..0000000
--- a/testdata/pokemon_types.golden
+++ /dev/null
@@ -1,8 +0,0 @@
-Your selected Pokémon: Armarouge
-• National Pokédex #: 936
-• Weight: 85.0kg (187.4 lbs)
-• Height: 4.9m (4′11″)
-──────
-Typing
-Type 1: Fire
-Type 2: Psychic