From 73dc3d7c50b23108a4864e3aba6b006bc1a231b2 Mon Sep 17 00:00:00 2001 From: Christian Sanchez Date: Tue, 21 Apr 2026 10:35:57 -0700 Subject: [PATCH 1/6] adding new `constants` package (#271) --- cmd/item/item.go | 3 ++- cmd/move/move.go | 5 +++-- constants/values.go | 8 ++++++++ flags/pokemonflagset.go | 3 ++- 4 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 constants/values.go diff --git a/cmd/item/item.go b/cmd/item/item.go index e2c0a2c..fd82ecf 100644 --- a/cmd/item/item.go +++ b/cmd/item/item.go @@ -9,6 +9,7 @@ import ( "charm.land/lipgloss/v2" "github.com/digitalghost-dev/poke-cli/cmd/utils" "github.com/digitalghost-dev/poke-cli/connections" + "github.com/digitalghost-dev/poke-cli/constants" "github.com/digitalghost-dev/poke-cli/structs" "github.com/digitalghost-dev/poke-cli/styling" ) @@ -78,7 +79,7 @@ func itemInfoContainer(output *strings.Builder, itemStruct structs.ItemJSONStruc fullDoc = lipgloss.JoinVertical(lipgloss.Top, capitalizedItem, itemCost, itemCategory, "---", "Description:", missingData) } else { for _, entry := range itemStruct.FlavorTextEntries { - if entry.Language.Name == "en" && entry.VersionGroup.Name == "sword-shield" { + if entry.Language.Name == "en" && entry.VersionGroup.Name == constants.VersionSwordShield { if entry.Text != "" { flavorTextEntry = entry.Text fullDoc = lipgloss.JoinVertical(lipgloss.Top, capitalizedItem, itemCost, itemCategory, "---", "Description:", flavorTextEntry) diff --git a/cmd/move/move.go b/cmd/move/move.go index 1a3ae4c..00c3573 100644 --- a/cmd/move/move.go +++ b/cmd/move/move.go @@ -10,6 +10,7 @@ import ( "charm.land/lipgloss/v2" "github.com/digitalghost-dev/poke-cli/cmd/utils" "github.com/digitalghost-dev/poke-cli/connections" + "github.com/digitalghost-dev/poke-cli/constants" "github.com/digitalghost-dev/poke-cli/structs" "github.com/digitalghost-dev/poke-cli/styling" "golang.org/x/text/cases" @@ -110,12 +111,12 @@ func moveEffectContainer(output *strings.Builder, moveStruct structs.MoveJSONStr continue } - if entry.VersionGroup.Name == "scarlet-violet" { + if entry.VersionGroup.Name == constants.VersionScarletViolet { sv = entry.FlavorText break } - if entry.VersionGroup.Name == "sword-shield" { + if entry.VersionGroup.Name == constants.VersionSwordShield { swsh = entry.FlavorText } } diff --git a/constants/values.go b/constants/values.go new file mode 100644 index 0000000..f918223 --- /dev/null +++ b/constants/values.go @@ -0,0 +1,8 @@ +package constants + +const ( + VersionScarletViolet = "scarlet-violet" + VersionSwordShield = "sword-shield" + VersionSunMoon = "sun-moon" + VersionXY = "x-y" +) diff --git a/flags/pokemonflagset.go b/flags/pokemonflagset.go index 5c6b567..27cfda0 100644 --- a/flags/pokemonflagset.go +++ b/flags/pokemonflagset.go @@ -19,6 +19,7 @@ import ( "charm.land/lipgloss/v2/table" cmdutils "github.com/digitalghost-dev/poke-cli/cmd/utils" "github.com/digitalghost-dev/poke-cli/connections" + "github.com/digitalghost-dev/poke-cli/constants" "github.com/digitalghost-dev/poke-cli/structs" "github.com/digitalghost-dev/poke-cli/styling" "github.com/disintegration/imaging" @@ -427,7 +428,7 @@ func MovesFlag(w io.Writer, endpoint string, pokemonName string) error { eligibleMoves := 0 for _, pokeMove := range pokemonStruct.Moves { for _, detail := range pokeMove.VersionGroupDetails { - if detail.VersionGroup.Name != "scarlet-violet" || detail.MoveLearnedMethod.Name != "level-up" { + if detail.VersionGroup.Name != constants.VersionScarletViolet || detail.MoveLearnedMethod.Name != "level-up" { continue } From dbe5b67c986a5faeb842319937da42b21628304f Mon Sep 17 00:00:00 2001 From: Christian Sanchez Date: Tue, 21 Apr 2026 10:40:08 -0700 Subject: [PATCH 2/6] updating version numbers --- .github/workflows/ci.yml | 2 +- .goreleaser.yml | 2 +- Dockerfile | 2 +- README.md | 12 ++++++------ card_data/pipelines/poke_cli_dbt/dbt_project.yml | 2 +- nfpm.yaml | 2 +- testdata/main_latest_flag.golden | 2 +- web/pyproject.toml | 2 +- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 924f972..41a9742 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ on: - main env: - VERSION_NUMBER: 'v1.9.3' + VERSION_NUMBER: 'v1.9.4' DOCKERHUB_REGISTRY_NAME: 'digitalghostdev/poke-cli' AWS_REGION: 'us-west-2' diff --git a/.goreleaser.yml b/.goreleaser.yml index 475906b..9228c2d 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -14,7 +14,7 @@ builds: - windows - darwin ldflags: - - -s -w -X main.version=v1.9.3 + - -s -w -X main.version=v1.9.4 archives: - formats: [ 'zip' ] diff --git a/Dockerfile b/Dockerfile index 7f43ac5..1ddfbd5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ RUN go mod download COPY . . -RUN go build -ldflags "-X main.version=v1.9.3" -o poke-cli . +RUN go build -ldflags "-X main.version=v1.9.4" -o poke-cli . # build 2 FROM --platform=$BUILDPLATFORM alpine:3.23 diff --git a/README.md b/README.md index 25f767e..c3fa3ea 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ pokemon-logo

version-label - docker-image-size + docker-image-size ci-status-badge
@@ -99,11 +99,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.9.3 [subcommand] [flag] + docker run --rm -it digitalghostdev/poke-cli:v1.9.4 [subcommand] [flag] ``` * Enter the container and use its shell: ```bash - docker run --rm -it --name poke-cli --entrypoint /bin/sh digitalghostdev/poke-cli:v1.9.3 -c "cd /app && exec sh" + docker run --rm -it --name poke-cli --entrypoint /bin/sh digitalghostdev/poke-cli:v1.9.4 -c "cd /app && exec sh" # placed into the /app directory, run the program with './poke-cli' # example: ./poke-cli ability swift-swim ``` @@ -112,13 +112,13 @@ Cloudsmith is a fully cloud-based service that lets you easily create, store, an > The `card` command renders TCG card images using your terminal's graphics protocol. When running inside Docker, pass your terminal's environment variables so image rendering works correctly: > ```bash > # Kitty -> docker run --rm -it -e TERM -e KITTY_WINDOW_ID digitalghostdev/poke-cli:v1.9.3 card +> docker run --rm -it -e TERM -e KITTY_WINDOW_ID digitalghostdev/poke-cli:v1.9.4 card > > # WezTerm, iTerm2, Ghostty, Konsole, Rio, Tabby -> docker run --rm -it -e TERM -e TERM_PROGRAM digitalghostdev/poke-cli:v1.9.3 card +> docker run --rm -it -e TERM -e TERM_PROGRAM digitalghostdev/poke-cli:v1.9.4 card > > # Windows Terminal (Sixel) -> docker run --rm -it -e WT_SESSION digitalghostdev/poke-cli:v1.9.3 card +> docker run --rm -it -e WT_SESSION digitalghostdev/poke-cli:v1.9.4 card > ``` > If your terminal is not listed above, image rendering is not supported inside Docker. diff --git a/card_data/pipelines/poke_cli_dbt/dbt_project.yml b/card_data/pipelines/poke_cli_dbt/dbt_project.yml index b87090d..dc9e11d 100644 --- a/card_data/pipelines/poke_cli_dbt/dbt_project.yml +++ b/card_data/pipelines/poke_cli_dbt/dbt_project.yml @@ -1,5 +1,5 @@ name: 'poke_cli_dbt' -version: '1.9.3' +version: '1.9.4' profile: 'poke_cli_dbt' diff --git a/nfpm.yaml b/nfpm.yaml index c3b5afa..86c4449 100644 --- a/nfpm.yaml +++ b/nfpm.yaml @@ -1,7 +1,7 @@ name: "poke-cli" arch: "arm64" platform: "linux" -version: "v1.9.3" +version: "v1.9.4" section: "default" version_schema: semver maintainer: "Christian S" diff --git a/testdata/main_latest_flag.golden b/testdata/main_latest_flag.golden index 5d9163c..2848c86 100644 --- a/testdata/main_latest_flag.golden +++ b/testdata/main_latest_flag.golden @@ -2,6 +2,6 @@ ┃ ┃ ┃ Latest available release ┃ ┃ on GitHub: ┃ -┃ • v1.9.2 ┃ +┃ • v1.9.3 ┃ ┃ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ diff --git a/web/pyproject.toml b/web/pyproject.toml index 8f1d0a3..b717b61 100644 --- a/web/pyproject.toml +++ b/web/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "web" -version = "1.9.3" +version = "1.9.4" description = "Streamlit dashboard for browsing and visualizing Pokémon TCG tournament standings and results." readme = "README.md" requires-python = ">=3.12" From a64ef6941e2c526d8569f5b6f827d3353a5fafa8 Mon Sep 17 00:00:00 2001 From: Christian Sanchez Date: Tue, 21 Apr 2026 14:56:45 -0700 Subject: [PATCH 3/6] adding user agent in API calls --- card_data/pipelines/defs/extract/tcgcsv/extract_pricing.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/card_data/pipelines/defs/extract/tcgcsv/extract_pricing.py b/card_data/pipelines/defs/extract/tcgcsv/extract_pricing.py index 5c9babe..a093fd1 100644 --- a/card_data/pipelines/defs/extract/tcgcsv/extract_pricing.py +++ b/card_data/pipelines/defs/extract/tcgcsv/extract_pricing.py @@ -137,14 +137,15 @@ def pull_product_information(set_number: str) -> pl.DataFrame: print(colored(" →", "blue"), f"Processing set: {set_number}") product_id = SET_PRODUCT_MATCHING[set_number] + headers = {"User-Agent": "poke-cli/1.9"} # Fetch product data products_url = f"https://tcgcsv.com/tcgplayer/3/{product_id}/products" - products_data = requests.get(products_url, timeout=30).json() + products_data = requests.get(products_url, timeout=30, headers=headers).json() # Fetch pricing data prices_url = f"https://tcgcsv.com/tcgplayer/3/{product_id}/prices" - prices_data = requests.get(prices_url, timeout=30).json() + prices_data = requests.get(prices_url, timeout=30, headers=headers).json() price_dict = { price["productId"]: price.get("marketPrice") From 08803f99e735d47dfbe1d57d48f874c5cb8a0210 Mon Sep 17 00:00:00 2001 From: Christian Sanchez Date: Wed, 22 Apr 2026 14:12:46 -0700 Subject: [PATCH 4/6] adding helper functions to `pokemon` command (#272) --- cmd/pokemon/pokemon.go | 161 +---------------- cmd/pokemon/render.go | 148 ++++++++++++++++ cmd/pokemon/render_test.go | 343 +++++++++++++++++++++++++++++++++++++ 3 files changed, 497 insertions(+), 155 deletions(-) create mode 100644 cmd/pokemon/render.go create mode 100644 cmd/pokemon/render_test.go diff --git a/cmd/pokemon/pokemon.go b/cmd/pokemon/pokemon.go index 51087d2..1e7da27 100644 --- a/cmd/pokemon/pokemon.go +++ b/cmd/pokemon/pokemon.go @@ -6,18 +6,13 @@ import ( "flag" "fmt" "io" - "math" "os" - "sort" "strings" - "charm.land/lipgloss/v2" "github.com/digitalghost-dev/poke-cli/cmd/utils" "github.com/digitalghost-dev/poke-cli/connections" "github.com/digitalghost-dev/poke-cli/flags" "github.com/digitalghost-dev/poke-cli/styling" - "golang.org/x/text/cases" - "golang.org/x/text/language" ) // PokemonCommand processes the Pokémon command @@ -83,150 +78,6 @@ func PokemonCommand() (string, error) { capitalizedString := styling.CapitalizeResourceName(pokemonName) - 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), " ") - - wrapped := utils.WrapText(flavorText, 60) - fmt.Fprintln(w, wrapped) - return - } - } - } - - eggInformation := func(w io.Writer) { - var eggInformationSlice []string - - modernEggInformationNames := map[string]string{ - "indeterminate": "Amorphous", - "ground": "Field", - "humanshape": "Human-Like", - "plant": "Grass", - "no-eggs": "Undiscovered", - } - - for _, entry := range pokemonSpeciesStruct.EggGroups { - if name, exists := modernEggInformationNames[entry.Name]; exists { - eggInformationSlice = append(eggInformationSlice, name) - } else { - eggInformationSlice = append(eggInformationSlice, cases.Title(language.English).String(entry.Name)) - } - } - - sort.Strings(eggInformationSlice) - - genderRate := pokemonSpeciesStruct.GenderRate - m := map[int]string{ - -1: "Genderless", - 0: "0% F", - 1: "12.5% F", - 2: "25% F", - 3: "37.5% F", - 4: "50% F", - 5: "62.5% F", - 6: "75% F", - 7: "87.5% F", - 8: "100% F", - } - - hatchCounter := pokemonSpeciesStruct.HatchCounter - - fmt.Fprintf(w, - "\n%s %s %s\n%s %s %s\n%s %s %d", - styling.ColoredBullet, - "Egg Group(s):", strings.Join(eggInformationSlice, ", "), - styling.ColoredBullet, - "Gender Rate:", m[genderRate], - styling.ColoredBullet, - "Egg Cycles:", hatchCounter, - ) - } - - effortValues := func(w io.Writer) { - nameMapping := map[string]string{ - "hp": "HP", - "attack": "Atk", - "defense": "Def", - "special-attack": "SpA", - "special-defense": "SpD", - "speed": "Spd", - } - - var evs []string - - for _, effortValue := range pokemonStruct.Stats { - if effortValue.Effort > 0 { - name, ok := nameMapping[effortValue.Stat.Name] - if !ok { - name = "Missing from API" - } - evs = append(evs, fmt.Sprintf("%d %s", effortValue.Effort, name)) - } - } - - fmt.Fprintf(w, "\n%s Effort Values: %s", styling.ColoredBullet, strings.Join(evs, ", ")) - } - - typing := func(w io.Writer) { - var typeBoxes []string - - for _, pokeType := range pokemonStruct.Types { - colorHex, exists := styling.ColorMap[pokeType.Type.Name] - if exists { - color := lipgloss.Color(colorHex) - typeColorStyle := lipgloss.NewStyle(). - Align(lipgloss.Center). - Foreground(lipgloss.Color("#FAFAFA")). - Background(color). - Margin(1, 1, 0, 0). - Height(1). - Width(14) - - rendered := typeColorStyle.Render(cases.Title(language.English).String(pokeType.Type.Name)) - typeBoxes = append(typeBoxes, rendered) - } - } - - joinedTypes := lipgloss.JoinHorizontal(lipgloss.Top, typeBoxes...) - fmt.Fprintln(w, joinedTypes) - } - - 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 := styling.CapitalizeResourceName(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 eggGroupOutput bytes.Buffer @@ -236,12 +87,12 @@ func PokemonCommand() (string, error) { effortValuesOutput bytes.Buffer ) - entry(&entryOutput) - eggInformation(&eggGroupOutput) - typing(&typeOutput) - metrics(&metricsOutput) - species(&speciesOutput) - effortValues(&effortValuesOutput) + renderEntry(&entryOutput, pokemonSpeciesStruct) + renderEggInformation(&eggGroupOutput, pokemonSpeciesStruct) + renderTyping(&typeOutput, pokemonStruct) + renderMetrics(&metricsOutput, pokemonStruct) + renderSpecies(&speciesOutput, pokemonSpeciesStruct) + renderEffortValues(&effortValuesOutput, pokemonStruct) fmt.Fprintf(&output, "Your selected Pokémon: %s\n%s\n%s%s%s%s%s\n", diff --git a/cmd/pokemon/render.go b/cmd/pokemon/render.go new file mode 100644 index 0000000..00e22ac --- /dev/null +++ b/cmd/pokemon/render.go @@ -0,0 +1,148 @@ +package pokemon + +import ( + "fmt" + "io" + "math" + "sort" + "strings" + + "charm.land/lipgloss/v2" + "github.com/digitalghost-dev/poke-cli/cmd/utils" + "github.com/digitalghost-dev/poke-cli/structs" + "github.com/digitalghost-dev/poke-cli/styling" + "golang.org/x/text/cases" + "golang.org/x/text/language" +) + +func renderEggInformation(w io.Writer, s structs.PokemonSpeciesJSONStruct) { + var eggInformationSlice []string + + modernEggInformationNames := map[string]string{ + "indeterminate": "Amorphous", + "ground": "Field", + "humanshape": "Human-Like", + "plant": "Grass", + "no-eggs": "Undiscovered", + } + + for _, entry := range s.EggGroups { + if name, exists := modernEggInformationNames[entry.Name]; exists { + eggInformationSlice = append(eggInformationSlice, name) + } else { + eggInformationSlice = append(eggInformationSlice, cases.Title(language.English).String(entry.Name)) + } + } + + sort.Strings(eggInformationSlice) + + m := map[int]string{ + -1: "Genderless", + 0: "0% F", + 1: "12.5% F", + 2: "25% F", + 3: "37.5% F", + 4: "50% F", + 5: "62.5% F", + 6: "75% F", + 7: "87.5% F", + 8: "100% F", + } + + fmt.Fprintf(w, + "\n%s %s %s\n%s %s %s\n%s %s %d", + styling.ColoredBullet, + "Egg Group(s):", strings.Join(eggInformationSlice, ", "), + styling.ColoredBullet, + "Gender Rate:", m[s.GenderRate], + styling.ColoredBullet, + "Egg Cycles:", s.HatchCounter, + ) +} + +func renderEffortValues(w io.Writer, s structs.PokemonJSONStruct) { + nameMapping := map[string]string{ + "hp": "HP", + "attack": "Atk", + "defense": "Def", + "special-attack": "SpA", + "special-defense": "SpD", + "speed": "Spd", + } + + var evs []string + + for _, effortValue := range s.Stats { + if effortValue.Effort > 0 { + name, ok := nameMapping[effortValue.Stat.Name] + if !ok { + name = "Missing from API" + } + evs = append(evs, fmt.Sprintf("%d %s", effortValue.Effort, name)) + } + } + + fmt.Fprintf(w, "\n%s Effort Values: %s", styling.ColoredBullet, strings.Join(evs, ", ")) +} + +func renderEntry(w io.Writer, s structs.PokemonSpeciesJSONStruct) { + for _, entry := range s.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), " ") + fmt.Fprintln(w, utils.WrapText(flavorText, 60)) + return + } + } +} + +func renderMetrics(w io.Writer, s structs.PokemonJSONStruct) { + weightKilograms := float64(s.Weight) / 10 + weightPounds := float64(weightKilograms) * 2.20462 + + heightMeters := float64(s.Height) / 10 + heightFeet := heightMeters * 3.28084 + feet := int(heightFeet) + inches := int(math.Round((heightFeet - float64(feet)) * 12)) + + 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, s.ID, + styling.ColoredBullet, weightKilograms, weightPounds, + styling.ColoredBullet, heightMeters, feet, inches) +} + +func renderSpecies(w io.Writer, s structs.PokemonSpeciesJSONStruct) { + if s.EvolvesFromSpecies.Name != "" { + capitalizedPokemonName := styling.CapitalizeResourceName(s.EvolvesFromSpecies.Name) + fmt.Fprintf(w, "%s %s %s", styling.ColoredBullet, "Evolves from:", capitalizedPokemonName) + } else { + fmt.Fprintf(w, "%s %s", styling.ColoredBullet, "Basic Pokémon") + } +} + +func renderTyping(w io.Writer, s structs.PokemonJSONStruct) { + var typeBoxes []string + + for _, pokeType := range s.Types { + colorHex, exists := styling.ColorMap[pokeType.Type.Name] + if exists { + typeColorStyle := lipgloss.NewStyle(). + Align(lipgloss.Center). + Foreground(lipgloss.Color("#FAFAFA")). + Background(lipgloss.Color(colorHex)). + Margin(1, 1, 0, 0). + Height(1). + Width(14) + + rendered := typeColorStyle.Render(cases.Title(language.English).String(pokeType.Type.Name)) + typeBoxes = append(typeBoxes, rendered) + } + } + + fmt.Fprintln(w, lipgloss.JoinHorizontal(lipgloss.Top, typeBoxes...)) +} diff --git a/cmd/pokemon/render_test.go b/cmd/pokemon/render_test.go new file mode 100644 index 0000000..f4f4177 --- /dev/null +++ b/cmd/pokemon/render_test.go @@ -0,0 +1,343 @@ +package pokemon + +import ( + "bytes" + "strings" + "testing" + + "github.com/digitalghost-dev/poke-cli/structs" + "github.com/stretchr/testify/assert" +) + +func TestRenderEntry(t *testing.T) { + tests := []struct { + name string + species structs.PokemonSpeciesJSONStruct + contains string + }{ + { + name: "scarlet entry preferred", + species: structs.PokemonSpeciesJSONStruct{ + 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"` + }{ + {FlavorText: "A shield entry.", Language: struct { + Name string `json:"name"` + URL string `json:"url"` + }{Name: "en"}, Version: struct { + Name string `json:"name"` + URL string `json:"url"` + }{Name: "shield"}}, + {FlavorText: "A scarlet entry.", Language: struct { + Name string `json:"name"` + URL string `json:"url"` + }{Name: "en"}, Version: struct { + Name string `json:"name"` + URL string `json:"url"` + }{Name: "scarlet"}}, + }, + }, + contains: "A shield entry.", + }, + { + name: "non-english entries are skipped", + species: structs.PokemonSpeciesJSONStruct{ + 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"` + }{ + {FlavorText: "Un texto en español.", Language: struct { + Name string `json:"name"` + URL string `json:"url"` + }{Name: "es"}, Version: struct { + Name string `json:"name"` + URL string `json:"url"` + }{Name: "scarlet"}}, + }, + }, + contains: "", + }, + { + name: "empty flavor text entries", + species: structs.PokemonSpeciesJSONStruct{}, + contains: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var buf bytes.Buffer + renderEntry(&buf, tt.species) + if tt.contains == "" { + assert.Empty(t, buf.String()) + } else { + assert.Contains(t, buf.String(), tt.contains) + } + }) + } +} + +func TestRenderEggInformation(t *testing.T) { + tests := []struct { + name string + species structs.PokemonSpeciesJSONStruct + contains []string + }{ + { + name: "known legacy egg group names are modernized", + species: structs.PokemonSpeciesJSONStruct{ + EggGroups: []struct { + Name string `json:"name"` + URL string `json:"url"` + }{{Name: "indeterminate"}, {Name: "ground"}}, + GenderRate: 4, + HatchCounter: 20, + }, + contains: []string{"Amorphous", "Field", "50% F", "20"}, + }, + { + name: "genderless pokemon", + species: structs.PokemonSpeciesJSONStruct{ + EggGroups: []struct { + Name string `json:"name"` + URL string `json:"url"` + }{{Name: "no-eggs"}}, + GenderRate: -1, + HatchCounter: 120, + }, + contains: []string{"Undiscovered", "Genderless", "120"}, + }, + { + name: "regular egg group title-cased", + species: structs.PokemonSpeciesJSONStruct{ + EggGroups: []struct { + Name string `json:"name"` + URL string `json:"url"` + }{{Name: "monster"}}, + GenderRate: 1, + HatchCounter: 5, + }, + contains: []string{"Monster", "12.5% F"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var buf bytes.Buffer + renderEggInformation(&buf, tt.species) + output := buf.String() + for _, s := range tt.contains { + assert.Contains(t, output, s) + } + }) + } +} + +func TestRenderEffortValues(t *testing.T) { + tests := []struct { + name string + pokemon structs.PokemonJSONStruct + contains string + absent string + }{ + { + name: "single EV stat", + pokemon: structs.PokemonJSONStruct{ + Stats: []struct { + BaseStat int `json:"base_stat"` + Effort int `json:"effort"` + Stat struct { + Name string `json:"name"` + } `json:"stat"` + }{ + {Effort: 2, Stat: struct{ Name string `json:"name"` }{Name: "speed"}}, + {Effort: 0, Stat: struct{ Name string `json:"name"` }{Name: "attack"}}, + }, + }, + contains: "2 Spd", + absent: "Atk", + }, + { + name: "unknown stat name falls back", + pokemon: structs.PokemonJSONStruct{ + Stats: []struct { + BaseStat int `json:"base_stat"` + Effort int `json:"effort"` + Stat struct { + Name string `json:"name"` + } `json:"stat"` + }{ + {Effort: 1, Stat: struct{ Name string `json:"name"` }{Name: "mystery-stat"}}, + }, + }, + contains: "Missing from API", + }, + { + name: "no EVs produces empty list", + pokemon: structs.PokemonJSONStruct{ + Stats: []struct { + BaseStat int `json:"base_stat"` + Effort int `json:"effort"` + Stat struct { + Name string `json:"name"` + } `json:"stat"` + }{ + {Effort: 0, Stat: struct{ Name string `json:"name"` }{Name: "hp"}}, + }, + }, + contains: "Effort Values:", + absent: "HP", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var buf bytes.Buffer + renderEffortValues(&buf, tt.pokemon) + output := buf.String() + assert.Contains(t, output, tt.contains) + if tt.absent != "" { + assert.NotContains(t, output, tt.absent) + } + }) + } +} + +func TestRenderMetrics(t *testing.T) { + tests := []struct { + name string + pokemon structs.PokemonJSONStruct + contains []string + }{ + { + name: "pikachu-like metrics", + pokemon: structs.PokemonJSONStruct{ID: 25, Weight: 60, Height: 4}, + contains: []string{ + "National Pokédex #: 25", + "6.0kg", + "0.4m", + }, + }, + { + name: "heavy pokemon", + pokemon: structs.PokemonJSONStruct{ID: 131, Weight: 2160, Height: 20}, + contains: []string{ + "National Pokédex #: 131", + "216.0kg", + "2.0m", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var buf bytes.Buffer + renderMetrics(&buf, tt.pokemon) + output := buf.String() + for _, s := range tt.contains { + assert.Contains(t, output, s) + } + }) + } +} + +func TestRenderSpecies(t *testing.T) { + tests := []struct { + name string + species structs.PokemonSpeciesJSONStruct + contains string + }{ + { + name: "evolves from a previous stage", + species: structs.PokemonSpeciesJSONStruct{ + EvolvesFromSpecies: struct { + Name string `json:"name"` + URL string `json:"url"` + }{Name: "pikachu"}, + }, + contains: "Evolves from: Pikachu", + }, + { + name: "basic pokemon with no pre-evolution", + species: structs.PokemonSpeciesJSONStruct{}, + contains: "Basic Pokémon", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var buf bytes.Buffer + renderSpecies(&buf, tt.species) + assert.Contains(t, buf.String(), tt.contains) + }) + } +} + +func TestRenderTyping(t *testing.T) { + tests := []struct { + name string + pokemon structs.PokemonJSONStruct + contains string + }{ + { + name: "fire type renders type name", + pokemon: structs.PokemonJSONStruct{ + Types: []struct { + Slot int `json:"slot"` + Type struct { + Name string `json:"name"` + URL string `json:"url"` + } `json:"type"` + }{{Type: struct { + Name string `json:"name"` + URL string `json:"url"` + }{Name: "fire"}}}, + }, + contains: "Fire", + }, + { + name: "unknown type is skipped", + pokemon: structs.PokemonJSONStruct{ + Types: []struct { + Slot int `json:"slot"` + Type struct { + Name string `json:"name"` + URL string `json:"url"` + } `json:"type"` + }{{Type: struct { + Name string `json:"name"` + URL string `json:"url"` + }{Name: "faketype"}}}, + }, + contains: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var buf bytes.Buffer + renderTyping(&buf, tt.pokemon) + output := buf.String() + if tt.contains == "" { + assert.True(t, strings.TrimSpace(output) == "" || !strings.Contains(output, "Faketype")) + } else { + assert.Contains(t, output, tt.contains) + } + }) + } +} From 0a6d4c8b05e8c5d540296c78e4af243d4323990d Mon Sep 17 00:00:00 2001 From: Christian Sanchez Date: Wed, 22 Apr 2026 14:12:59 -0700 Subject: [PATCH 5/6] updating tests --- structs/structs_test.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/structs/structs_test.go b/structs/structs_test.go index 3a70b83..f50ee52 100644 --- a/structs/structs_test.go +++ b/structs/structs_test.go @@ -3,8 +3,19 @@ package structs import ( "encoding/json" "testing" + + "github.com/stretchr/testify/assert" ) +func TestGetResourceName(t *testing.T) { + assert.Equal(t, "strong-jaw", AbilityJSONStruct{Name: "strong-jaw"}.GetResourceName()) + assert.Equal(t, "poke-ball", ItemJSONStruct{Name: "poke-ball"}.GetResourceName()) + assert.Equal(t, "thunderbolt", MoveJSONStruct{Name: "thunderbolt"}.GetResourceName()) + assert.Equal(t, "pikachu", PokemonJSONStruct{Name: "pikachu"}.GetResourceName()) + assert.Equal(t, "pikachu", PokemonSpeciesJSONStruct{Name: "pikachu"}.GetResourceName()) + assert.Equal(t, "fire", TypesJSONStruct{Name: "fire"}.GetResourceName()) +} + func TestPokemonJSONStruct_Unmarshal(t *testing.T) { // Sample JSON data for a Pokémon jsonData := `{ From eb9983c7c50d261612f652e3414a7d7fe9ff2fae Mon Sep 17 00:00:00 2001 From: Christian Sanchez Date: Thu, 23 Apr 2026 09:20:42 -0700 Subject: [PATCH 6/6] updating tests --- cmd/pokemon/render_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/cmd/pokemon/render_test.go b/cmd/pokemon/render_test.go index f4f4177..1d4c338 100644 --- a/cmd/pokemon/render_test.go +++ b/cmd/pokemon/render_test.go @@ -16,7 +16,7 @@ func TestRenderEntry(t *testing.T) { contains string }{ { - name: "scarlet entry preferred", + name: "first matching english entry returned", species: structs.PokemonSpeciesJSONStruct{ FlavorTextEntries: []struct { FlavorText string `json:"flavor_text"` @@ -29,23 +29,23 @@ func TestRenderEntry(t *testing.T) { URL string `json:"url"` } `json:"version"` }{ - {FlavorText: "A shield entry.", Language: struct { + {FlavorText: "A scarlet entry.", Language: struct { Name string `json:"name"` URL string `json:"url"` }{Name: "en"}, Version: struct { Name string `json:"name"` URL string `json:"url"` - }{Name: "shield"}}, - {FlavorText: "A scarlet entry.", Language: struct { + }{Name: "scarlet"}}, + {FlavorText: "A shield entry.", Language: struct { Name string `json:"name"` URL string `json:"url"` }{Name: "en"}, Version: struct { Name string `json:"name"` URL string `json:"url"` - }{Name: "scarlet"}}, + }{Name: "shield"}}, }, }, - contains: "A shield entry.", + contains: "A scarlet entry.", }, { name: "non-english entries are skipped", @@ -334,7 +334,7 @@ func TestRenderTyping(t *testing.T) { renderTyping(&buf, tt.pokemon) output := buf.String() if tt.contains == "" { - assert.True(t, strings.TrimSpace(output) == "" || !strings.Contains(output, "Faketype")) + assert.Empty(t, strings.TrimSpace(output)) } else { assert.Contains(t, output, tt.contains) }