diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6b0c9a49..3cf6bfd7 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -26,7 +26,7 @@ on:
- main
env:
- VERSION_NUMBER: 'v1.2.1'
+ VERSION_NUMBER: 'v1.2.2'
DOCKERHUB_REGISTRY_NAME: 'digitalghostdev/poke-cli'
AWS_REGION: 'us-west-2'
diff --git a/.goreleaser.yaml b/.goreleaser.yaml
index fc81a6eb..08ad1b45 100644
--- a/.goreleaser.yaml
+++ b/.goreleaser.yaml
@@ -14,7 +14,7 @@ builds:
- windows
- darwin
ldflags:
- - -s -w -X main.version=v1.2.1
+ - -s -w -X main.version=v1.2.2
archives:
- format: tar.gz
diff --git a/Dockerfile b/Dockerfile
index 936228e4..48696a02 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -8,7 +8,7 @@ RUN go mod download
COPY . .
-RUN go build -ldflags "-X main.version=v1.2.1" -o poke-cli .
+RUN go build -ldflags "-X main.version=v1.2.2" -o poke-cli .
# build 2
FROM --platform=$BUILDPLATFORM alpine:latest
diff --git a/README.md b/README.md
index 9e6fabc2..e4e20b68 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
Pokémon CLI
-
+
@@ -76,11 +76,11 @@ View future plans in the [Roadmap](#roadmap) section.
3. Choose how to interact with the container:
* Run a single command and exit:
```bash
- docker run --rm -it digitalghostdev/poke-cli:v1.2.1 [subcommand] flag]
+ docker run --rm -it digitalghostdev/poke-cli:v1.2.2 [subcommand] flag]
```
* Enter the container and use its shell:
```bash
- docker run --rm -it --name poke-cli --entrypoint /bin/sh digitalghostdev/poke-cli:v1.2.1 -c "cd /app && exec sh"
+ docker run --rm -it --name poke-cli --entrypoint /bin/sh digitalghostdev/poke-cli:v1.2.2 -c "cd /app && exec sh"
# placed into the /app directory, run the program with './poke-cli'
# example: ./poke-cli ability swift-swim
```
@@ -145,7 +145,7 @@ Below is a list of the planned/completed commands and flags:
- [x] `move`: get data about a specific move.
- [ ] `-p | --pokemon`: display Pokémon that learn this move.
- [x] `natures`: get data about natures.
-- [ ] `pokemon`: get data about a specific Pokémon.
+- [x] `pokemon`: get data about a specific Pokémon.
- [x] `-a | --abilities`: display the Pokémon's abilities.
- [x] `-i | --image`: display a pixel image of the Pokémon.
- [x] `-s | --stats`: display the Pokémon's base stats.
diff --git a/cli.go b/cli.go
index 7b1b0be8..a5335fc9 100644
--- a/cli.go
+++ b/cli.go
@@ -6,6 +6,7 @@ import (
"github.com/digitalghost-dev/poke-cli/cmd"
"github.com/digitalghost-dev/poke-cli/cmd/move"
"github.com/digitalghost-dev/poke-cli/cmd/search"
+ "github.com/digitalghost-dev/poke-cli/cmd/types"
"github.com/digitalghost-dev/poke-cli/flags"
"github.com/digitalghost-dev/poke-cli/styling"
"os"
@@ -92,7 +93,7 @@ func runCLI(args []string) int {
"move": move.MoveCommand,
"natures": cmd.NaturesCommand,
"pokemon": cmd.PokemonCommand,
- "types": cmd.TypesCommand,
+ "types": types.TypesCommand,
"search": search.SearchCommand,
}
diff --git a/cmd/ability.go b/cmd/ability.go
index f1864dc3..2fb6609c 100644
--- a/cmd/ability.go
+++ b/cmd/ability.go
@@ -33,6 +33,11 @@ func AbilityCommand() {
flag.Parse()
+ if len(os.Args) == 3 && (os.Args[2] == "-h" || os.Args[2] == "--help") {
+ flag.Usage()
+ return
+ }
+
if err := ValidateAbilityArgs(args); err != nil {
fmt.Println(err.Error())
if os.Getenv("GO_TESTING") != "1" {
diff --git a/cmd/move/move.go b/cmd/move/move.go
index 8417c344..4daf90f7 100644
--- a/cmd/move/move.go
+++ b/cmd/move/move.go
@@ -20,17 +20,23 @@ func MoveCommand() {
helpMessage := styling.HelpBorder.Render(
"Get details about a specific move.\n\n",
styling.StyleBold.Render("USAGE:"),
- fmt.Sprintf("\n\t%s %s %s %s", "poke-cli", styling.StyleBold.Render("move"), "", "[flag]"),
- fmt.Sprintf("\n\t%-20s", styling.StyleItalic.Render("Use a hyphen when typing a name with a space.")),
- "\n\n",
- styling.StyleBold.Render("FLAGS:"),
- fmt.Sprintf("\n\t%-20s %s", "-h, --help", "Prints the help menu."),
+ "\n\t"+"poke-cli"+" "+styling.StyleBold.Render("move")+" ",
+ "\n\n"+styling.StyleItalic.Render("Use a hyphen when typing a name with a space."),
)
fmt.Println(helpMessage)
}
flag.Parse()
+ // Check for help flag
+ if len(os.Args) == 3 && (os.Args[2] == "-h" || os.Args[2] == "--help") {
+ flag.Usage()
+
+ if flag.Lookup("test.v") == nil {
+ os.Exit(0)
+ }
+ }
+
if err := cmd.ValidateMoveArgs(os.Args); err != nil {
fmt.Println(err.Error())
os.Exit(1)
diff --git a/cmd/natures.go b/cmd/natures.go
index 298762b0..5764781e 100644
--- a/cmd/natures.go
+++ b/cmd/natures.go
@@ -9,6 +9,8 @@ import (
"os"
)
+var osExit = os.Exit
+
func NaturesCommand() {
flag.Usage = func() {
helpMessage := styling.HelpBorder.Render(
@@ -21,9 +23,15 @@ func NaturesCommand() {
flag.Parse()
+ if len(os.Args) == 3 && (os.Args[2] == "-h" || os.Args[2] == "--help") {
+ flag.Usage()
+ return
+ }
+
if err := ValidateNaturesArgs(os.Args); err != nil {
fmt.Println(err.Error())
- os.Exit(1)
+ osExit(1)
+ return
}
fmt.Println("Natures affect the growth of a Pokémon.\n" +
@@ -48,7 +56,7 @@ func NaturesCommand() {
Rows(chart...).
StyleFunc(func(row, col int) lipgloss.Style {
return lipgloss.NewStyle().
- Padding(0, 1) // This styles the border color
+ Padding(0, 1)
})
fmt.Println(t.Render())
diff --git a/cmd/natures_test.go b/cmd/natures_test.go
index afcd4f86..db0b5934 100644
--- a/cmd/natures_test.go
+++ b/cmd/natures_test.go
@@ -2,38 +2,33 @@ package cmd
import (
"bytes"
- "fmt"
"github.com/digitalghost-dev/poke-cli/styling"
+ "github.com/stretchr/testify/assert"
"os"
- "strings"
"testing"
)
+var exitCode int
+
+func fakeExit(code int) {
+ exitCode = code
+ panic("exit")
+}
+
func captureNaturesOutput(f func()) string {
- // Create a pipe to capture standard output
r, w, _ := os.Pipe()
- defer func(r *os.File) {
- err := r.Close()
- if err != nil {
- fmt.Println(err)
- }
- }(r)
-
- // Redirect os.Stdout to the write end of the pipe
+ defer func() {
+ _ = r.Close()
+ }()
+
oldStdout := os.Stdout
os.Stdout = w
defer func() { os.Stdout = oldStdout }()
- // Run the function
f()
- // Close the write end of the pipe
- err := w.Close()
- if err != nil {
- return ""
- }
+ _ = w.Close()
- // Read the captured output
var buf bytes.Buffer
_, _ = buf.ReadFrom(r)
return buf.String()
@@ -59,46 +54,56 @@ func TestNaturesCommand(t *testing.T) {
expectedError: false,
},
{
- name: "Valid Execution",
+ name: "Invalid extra argument",
+ args: []string{"natures", "extra"},
+ expectedOutput: styling.StripANSI(styling.ErrorBorder.Render(styling.ErrorColor.Render("Error!")+"\nThe only currently available options\nafter command are '-h' or '--help'")) + "\n",
+ expectedError: true,
+ },
+ {
+ name: "Full Natures output with table",
args: []string{"natures"},
- expectedOutput: styling.StripANSI(
- "Natures affect the growth of a Pokémon.\n" +
- "Each nature increases one of its stats by 10% and decreases one by 10%.\n" +
- "Five natures increase and decrease the same stat and therefore have no effect.\n\n" +
- "Nature Chart:\n" +
- "┌──────────┬─────────┬──────────┬──────────┬──────────┬─────────┐\n" +
- "│ │ -Attack │ -Defense │ -Sp. Atk │ -Sp. Def │ Speed │\n" +
- "├──────────┼─────────┼──────────┼──────────┼──────────┼─────────┤\n" +
- "│ +Attack │ Hardy │ Lonely │ Adamant │ Naughty │ Brave │\n" +
- "├──────────┼─────────┼──────────┼──────────┼──────────┼─────────┤\n" +
- "│ +Defense │ Bold │ Docile │ Impish │ Lax │ Relaxed │\n" +
- "├──────────┼─────────┼──────────┼──────────┼──────────┼─────────┤\n" +
- "│ +Sp. Atk │ Modest │ Mild │ Bashful │ Rash │ Quiet │\n" +
- "├──────────┼─────────┼──────────┼──────────┼──────────┼─────────┤\n" +
- "│ +Sp. Def │ Calm │ Gentle │ Careful │ Quirky │ Sassy │\n" +
- "├──────────┼─────────┼──────────┼──────────┼──────────┼─────────┤\n" +
- "│ Speed │ Timid │ Hasty │ Jolly │ Naive │ Serious │\n" +
- "└──────────┴─────────┴──────────┴──────────┴──────────┴─────────┘\n"),
+ expectedOutput: "Natures affect the growth of a Pokémon.\n" +
+ "Each nature increases one of its stats by 10% and decreases one by 10%.\n" +
+ "Five natures increase and decrease the same stat and therefore have no effect.\n\n" +
+ "Nature Chart:\n" +
+ "┌──────────┬─────────┬──────────┬──────────┬──────────┬─────────┐\n" +
+ "│ │ -Attack │ -Defense │ -Sp. Atk │ -Sp. Def │ Speed │\n" +
+ "├──────────┼─────────┼──────────┼──────────┼──────────┼─────────┤\n" +
+ "│ +Attack │ Hardy │ Lonely │ Adamant │ Naughty │ Brave │\n" +
+ "├──────────┼─────────┼──────────┼──────────┼──────────┼─────────┤\n" +
+ "│ +Defense │ Bold │ Docile │ Impish │ Lax │ Relaxed │\n" +
+ "├──────────┼─────────┼──────────┼──────────┼──────────┼─────────┤\n" +
+ "│ +Sp. Atk │ Modest │ Mild │ Bashful │ Rash │ Quiet │\n" +
+ "├──────────┼─────────┼──────────┼──────────┼──────────┼─────────┤\n" +
+ "│ +Sp. Def │ Calm │ Gentle │ Careful │ Quirky │ Sassy │\n" +
+ "├──────────┼─────────┼──────────┼──────────┼──────────┼─────────┤\n" +
+ "│ Speed │ Timid │ Hasty │ Jolly │ Naive │ Serious │\n" +
+ "└──────────┴─────────┴──────────┴──────────┴──────────┴─────────┘\n",
expectedError: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
+ // Override osExit
+ oldExit := osExit
+ osExit = fakeExit
+ defer func() { osExit = oldExit }()
+
+ // Reset captured exit code
+ exitCode = 0
+
// Save original os.Args
originalArgs := os.Args
defer func() { os.Args = originalArgs }()
-
- // Set os.Args for the test
os.Args = append([]string{"poke-cli"}, tt.args...)
- // Capture the output
+ // Capture output
output := captureNaturesOutput(func() {
defer func() {
- // Recover from os.Exit calls
if r := recover(); r != nil {
- if !tt.expectedError {
- t.Fatalf("Unexpected error: %v", r)
+ if r != "exit" {
+ t.Fatalf("Unexpected panic: %v", r)
}
}
}()
@@ -107,9 +112,16 @@ func TestNaturesCommand(t *testing.T) {
cleanOutput := styling.StripANSI(output)
- // Check output
- if !strings.Contains(cleanOutput, tt.expectedOutput) {
- t.Errorf("Output mismatch.\nExpected to contain:\n%s\nGot:\n%s", tt.expectedOutput, output)
+ // Logging expected vs actual
+ t.Logf("Expected Output:\n%s", tt.expectedOutput)
+ t.Logf("Actual Output:\n%s", cleanOutput)
+
+ // Assertions
+ assert.Equal(t, tt.expectedOutput, cleanOutput, "Output should match expected")
+ if tt.expectedError {
+ assert.Equal(t, 1, exitCode, "Expected exit code 1 on error")
+ } else {
+ assert.Equal(t, 0, exitCode, "Expected no exit (code 0) on success")
}
})
}
diff --git a/cmd/pokemon.go b/cmd/pokemon.go
index 669b537b..34889103 100644
--- a/cmd/pokemon.go
+++ b/cmd/pokemon.go
@@ -57,6 +57,11 @@ func PokemonCommand() {
flag.Parse()
+ if len(os.Args) == 3 && (os.Args[2] == "-h" || os.Args[2] == "--help") {
+ flag.Usage()
+ return
+ }
+
err := ValidatePokemonArgs(args)
if err != nil {
fmt.Println(err.Error())
diff --git a/cmd/types.go b/cmd/types/damage_table.go
similarity index 53%
rename from cmd/types.go
rename to cmd/types/damage_table.go
index f1ee2e7f..1f91e4fa 100644
--- a/cmd/types.go
+++ b/cmd/types/damage_table.go
@@ -1,10 +1,7 @@
-package cmd
+package types
import (
- "flag"
"fmt"
- "github.com/charmbracelet/bubbles/table"
- tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/x/term"
"github.com/digitalghost-dev/poke-cli/connections"
@@ -15,47 +12,8 @@ import (
"strings"
)
-type model struct {
- table table.Model
- selectedOption string // Track the selected option
-}
-
-func (m model) Init() tea.Cmd { return nil }
-
-func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
- var cmd tea.Cmd
- switch msg := msg.(type) {
- case tea.KeyMsg:
- switch msg.String() {
- case "q", "esc", "ctrl+c":
- m.selectedOption = "quit"
- return m, tea.Quit
- case "enter":
- selectedRow := m.table.SelectedRow()
- m.selectedOption = selectedRow[0]
- return m, tea.Batch(
- tea.Quit,
- )
- }
- }
- m.table, cmd = m.table.Update(msg)
- return m, cmd
-}
-
-func (m model) View() string {
- // When an option is selected, no longer display the table.
- if m.selectedOption != "" {
- return ""
- }
- // Otherwise, display the table
- return "Select a type!\n" +
- styling.TypesTableBorder.Render(m.table.View()) +
- "\n" +
- styling.KeyMenu.Render("↑ (move up) • ↓ (move down)\nenter (select) • ctrl+c | esc (quit)")
-}
-
-// Function to display type details after a type is selected
-func displayTypeDetails(typesName string, endpoint string) {
+// DamageTable Function to display type details after a type is selected
+func DamageTable(typesName string, endpoint string) {
// Setting up variables to style the list
var columnWidth = 11
var subtle = lipgloss.AdaptiveColor{Light: "#D9DCCF", Dark: "#383838"}
@@ -139,70 +97,3 @@ func displayTypeDetails(typesName string, endpoint string) {
// Print the rendered document
fmt.Println(docStyle.Render(doc.String()))
}
-
-// Function that generates and handles the type selection table
-func tableGeneration(endpoint string) table.Model {
- columns := []table.Column{{Title: "Type", Width: 16}}
- rows := []table.Row{
- {"Normal"}, {"Fire"}, {"Water"}, {"Electric"}, {"Grass"}, {"Ice"},
- {"Fighting"}, {"Poison"}, {"Ground"}, {"Flying"}, {"Psychic"}, {"Bug"},
- {"Rock"}, {"Ghost"}, {"Dragon"}, {"Steel"}, {"Fairy"},
- }
-
- t := table.New(
- table.WithColumns(columns),
- table.WithRows(rows),
- table.WithFocused(true),
- table.WithHeight(7),
- )
-
- s := table.DefaultStyles()
- s.Header = s.Header.BorderStyle(lipgloss.NormalBorder()).BorderForeground(lipgloss.Color("#FFCC00")).BorderBottom(true)
- s.Selected = s.Selected.Foreground(lipgloss.Color("#000")).Background(lipgloss.Color("#FFCC00"))
- t.SetStyles(s)
-
- m := model{table: t}
- programModel, err := tea.NewProgram(m).Run()
- if err != nil {
- fmt.Println("Error running program:", err)
- os.Exit(1)
- }
-
- // Access the selected option from the model
- finalModel, ok := programModel.(model)
- if !ok {
- fmt.Println("Error: could not retrieve final model")
- os.Exit(1)
- }
-
- if finalModel.selectedOption != "quit" {
- typesName := strings.ToLower(finalModel.selectedOption)
- displayTypeDetails(typesName, endpoint) // Call function to display type details
- }
-
- return t
-}
-
-func TypesCommand() {
- flag.Usage = func() {
- helpMessage := styling.HelpBorder.Render(
- "Get details about a specific typing.\n\n",
- styling.StyleBold.Render("USAGE:"),
- fmt.Sprintf("\n\t%s %s %s", "poke-cli", styling.StyleBold.Render("types"), "[flag]"),
- "\n\n",
- styling.StyleBold.Render("FLAGS:"),
- fmt.Sprintf("\n\t%-30s %s", "-h, --help", "Prints out the help menu."),
- )
- fmt.Println(helpMessage)
- }
-
- flag.Parse()
-
- if err := ValidateTypesArgs(os.Args); err != nil {
- fmt.Println(err.Error())
- os.Exit(1)
- }
-
- endpoint := strings.ToLower(os.Args[1])[0:4]
- tableGeneration(endpoint)
-}
diff --git a/cmd/types/damage_table_test.go b/cmd/types/damage_table_test.go
new file mode 100644
index 00000000..f0fab2df
--- /dev/null
+++ b/cmd/types/damage_table_test.go
@@ -0,0 +1,43 @@
+package types
+
+import (
+ "bytes"
+ "os"
+ "strings"
+ "testing"
+)
+
+func TestDamageTable(t *testing.T) {
+ originalStdout := os.Stdout
+
+ r, w, err := os.Pipe()
+ if err != nil {
+ t.Fatalf("Failed to create pipe: %v", err)
+ }
+
+ os.Stdout = w
+
+ DamageTable("fire", "type")
+
+ err = w.Close()
+ if err != nil {
+ t.Fatalf("Failed to close pipe: %v", err)
+ }
+ os.Stdout = originalStdout
+
+ var buf bytes.Buffer
+ _, err = buf.ReadFrom(r)
+ if err != nil {
+ t.Fatalf("Failed to read from pipe: %v", err)
+ }
+ output := buf.String()
+
+ // Step 7: Assert the output contains expected strings
+ if !strings.Contains(output, "You selected the Fire type.") {
+ t.Errorf("Expected output to contain Fire type header, got:\n%s", output)
+ }
+
+ if !strings.Contains(output, "Damage Chart:") {
+ t.Errorf("Expected output to contain 'Damage Chart:', got:\n%s", output)
+ }
+}
diff --git a/cmd/types/types.go b/cmd/types/types.go
new file mode 100644
index 00000000..5e221a24
--- /dev/null
+++ b/cmd/types/types.go
@@ -0,0 +1,115 @@
+package types
+
+import (
+ "flag"
+ "fmt"
+ "github.com/charmbracelet/bubbles/table"
+ tea "github.com/charmbracelet/bubbletea"
+ "github.com/charmbracelet/lipgloss"
+ "github.com/digitalghost-dev/poke-cli/cmd"
+ "github.com/digitalghost-dev/poke-cli/styling"
+ "os"
+ "strings"
+)
+
+func TypesCommand() {
+ flag.Usage = func() {
+ helpMessage := styling.HelpBorder.Render(
+ "Get details about a specific typing.\n\n",
+ styling.StyleBold.Render("USAGE:"),
+ fmt.Sprintf("\n\t%s %s %s", "poke-cli", styling.StyleBold.Render("types"), "[flag]"),
+ "\n\n",
+ styling.StyleBold.Render("FLAGS:"),
+ fmt.Sprintf("\n\t%-30s %s", "-h, --help", "Prints out the help menu"),
+ )
+ fmt.Println(helpMessage)
+ }
+
+ flag.Parse()
+
+ if len(os.Args) == 3 && (os.Args[2] == "-h" || os.Args[2] == "--help") {
+ flag.Usage()
+ return
+ }
+
+ if err := cmd.ValidateTypesArgs(os.Args); err != nil {
+ fmt.Println(err.Error())
+ os.Exit(1)
+ }
+
+ endpoint := strings.ToLower(os.Args[1])[0:4]
+ tableGeneration(endpoint)
+}
+
+type model struct {
+ table table.Model
+ selectedOption string
+}
+
+func (m model) Init() tea.Cmd { return nil }
+
+func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
+ var bubbleCmd tea.Cmd
+ switch msg := msg.(type) {
+ case tea.KeyMsg:
+ switch msg.String() {
+ case "esc", "ctrl+c":
+ return m, tea.Quit
+ case "enter":
+ m.selectedOption = m.table.SelectedRow()[0]
+ return m, tea.Quit
+ }
+ }
+
+ m.table, bubbleCmd = m.table.Update(msg)
+ return m, bubbleCmd
+}
+
+func (m model) View() string {
+ if m.selectedOption != "" {
+ return ""
+ }
+
+ return fmt.Sprintf("Select a type!\n%s\n%s",
+ styling.TypesTableBorder.Render(m.table.View()),
+ styling.KeyMenu.Render("↑ (move up) • ↓ (move down)\nenter (select) • ctrl+c | esc (quit)"))
+}
+
+// Function that generates and handles the type selection table
+func tableGeneration(endpoint string) table.Model {
+ types := []string{"Normal", "Fire", "Water", "Electric", "Grass", "Ice",
+ "Fighting", "Poison", "Ground", "Flying", "Psychic", "Bug",
+ "Rock", "Ghost", "Dragon", "Steel", "Fairy"}
+
+ rows := make([]table.Row, len(types))
+ for i, t := range types {
+ rows[i] = []string{t}
+ }
+
+ t := table.New(
+ table.WithColumns([]table.Column{{Title: "Type", Width: 16}}),
+ table.WithRows(rows),
+ table.WithFocused(true),
+ table.WithHeight(7),
+ )
+
+ s := table.DefaultStyles()
+ s.Header = s.Header.
+ BorderStyle(lipgloss.NormalBorder()).
+ BorderForeground(lipgloss.Color("#FFCC00")).
+ BorderBottom(true)
+ s.Selected = s.Selected.
+ Foreground(lipgloss.Color("#000")).
+ Background(lipgloss.Color("#FFCC00"))
+ t.SetStyles(s)
+
+ m := model{table: t}
+ if programModel, err := tea.NewProgram(m).Run(); err != nil {
+ fmt.Println("Error running program:", err)
+ os.Exit(1)
+ } else if finalModel, ok := programModel.(model); ok && finalModel.selectedOption != "quit" {
+ DamageTable(strings.ToLower(finalModel.selectedOption), endpoint)
+ }
+
+ return t
+}
diff --git a/cmd/types/types_test.go b/cmd/types/types_test.go
new file mode 100644
index 00000000..e0510be2
--- /dev/null
+++ b/cmd/types/types_test.go
@@ -0,0 +1,102 @@
+package types
+
+import (
+ "bytes"
+ "fmt"
+ "github.com/digitalghost-dev/poke-cli/styling"
+ "github.com/stretchr/testify/assert"
+ "os"
+ "testing"
+)
+
+func captureTypesOutput(f func()) string {
+ r, w, _ := os.Pipe()
+ defer func(r *os.File) {
+ err := r.Close()
+ if err != nil {
+ fmt.Println(err)
+ }
+ }(r)
+
+ oldStdout := os.Stdout
+ os.Stdout = w
+ defer func() { os.Stdout = oldStdout }()
+
+ f()
+ err := w.Close()
+ if err != nil {
+ return ""
+ }
+
+ var buf bytes.Buffer
+ _, _ = buf.ReadFrom(r)
+ return buf.String()
+}
+
+func TestTypesCommand(t *testing.T) {
+ tests := []struct {
+ name string
+ args []string
+ expectedOutput string
+ expectedError bool
+ }{
+ {
+ name: "Help flag",
+ args: []string{"types", "-h"},
+ expectedOutput: styling.StripANSI(
+ "╭───────────────────────────────────────────────────────────╮\n" +
+ "│Get details about a specific typing. │\n" +
+ "│ │\n" +
+ "│ USAGE: │\n" +
+ "│ poke-cli types [flag] │\n" +
+ "│ │\n" +
+ "│ FLAGS: │\n" +
+ "│ -h, --help Prints out the help menu│\n" +
+ "╰───────────────────────────────────────────────────────────╯\n"),
+ expectedError: false,
+ },
+ {
+ name: "Help flag",
+ args: []string{"types", "--help"},
+ expectedOutput: styling.StripANSI(
+ "╭───────────────────────────────────────────────────────────╮\n" +
+ "│Get details about a specific typing. │\n" +
+ "│ │\n" +
+ "│ USAGE: │\n" +
+ "│ poke-cli types [flag] │\n" +
+ "│ │\n" +
+ "│ FLAGS: │\n" +
+ "│ -h, --help Prints out the help menu│\n" +
+ "╰───────────────────────────────────────────────────────────╯\n"),
+ expectedError: false,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ originalArgs := os.Args
+ defer func() { os.Args = originalArgs }()
+
+ os.Args = append([]string{"poke-cli"}, tt.args...)
+
+ output := captureTypesOutput(func() {
+ defer func() {
+ if r := recover(); r != nil && !tt.expectedError {
+ t.Fatalf("Unexpected error: %v", r)
+ }
+ }()
+ TypesCommand()
+ })
+
+ cleanOutput := styling.StripANSI(output)
+
+ assert.Equal(t, tt.expectedOutput, cleanOutput, "Output should contain the expected string")
+ })
+ }
+}
+
+func TestModelInit(t *testing.T) {
+ m := model{}
+ cmd := m.Init()
+ assert.Nil(t, cmd, "Init() should return nil")
+}
diff --git a/cmd/types_test.go b/cmd/types_test.go
deleted file mode 100644
index aa4c3a69..00000000
--- a/cmd/types_test.go
+++ /dev/null
@@ -1,54 +0,0 @@
-package cmd
-
-import (
- "github.com/digitalghost-dev/poke-cli/styling"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- "testing"
-)
-
-func TestValidateTypesArgs_ValidInput(t *testing.T) {
- validInputs := [][]string{
- {"poke-cli", "types"},
- {"poke-cli", "types", "-h"},
- }
-
- for _, input := range validInputs {
- err := ValidateTypesArgs(input)
- assert.NoError(t, err, "Expected no error for valid input")
- }
-}
-
-func TestValidateTypesArgs_TooManyArgs(t *testing.T) {
- invalidInputs := [][]string{
- {"poke-cli", "types", "ground"},
- }
- expectedError := "error, too many arguments\n"
-
- for _, input := range invalidInputs {
- err := ValidateTypesArgs(input)
- require.Error(t, err, "Expected error for too many arguments")
- assert.NotEqual(t, expectedError, err.Error())
- }
-}
-
-func TestModelInit(t *testing.T) {
- m := model{}
- result := m.Init()
-
- assert.Nil(t, result, "Expected Init() to return nil")
-}
-
-func TestModelView_DisplayTable(t *testing.T) {
- m := model{selectedOption: ""}
-
- // Construct the expected output exactly as `View()` should render it
- expectedOutput := "Select a type!\n" +
- styling.TypesTableBorder.Render(m.table.View()) +
- "\n" +
- styling.KeyMenu.Render("↑ (move up) • ↓ (move down)\nenter (select) • ctrl+c | esc (quit)")
-
- output := m.View()
-
- assert.Equal(t, expectedOutput, output, "Expected View output to include table view")
-}
diff --git a/cmd/validateargs.go b/cmd/validateargs.go
index 81db1001..fda38ff1 100644
--- a/cmd/validateargs.go
+++ b/cmd/validateargs.go
@@ -1,29 +1,25 @@
package cmd
import (
- "flag"
"fmt"
"github.com/digitalghost-dev/poke-cli/styling"
- "os"
)
-func handleHelpFlag(args []string) {
- if len(args) == 3 && (args[2] == "-h" || args[2] == "--help") {
- flag.Usage()
-
- if flag.Lookup("test.v") == nil {
- os.Exit(0)
- }
+// checkLength checks if the number of arguments is lower than the max value
+func checkLength(args []string, max int) error {
+ if len(args) > max {
+ errMessage := styling.ErrorBorder.Render(
+ styling.ErrorColor.Render("Error!") + "\nToo many arguments",
+ )
+ return fmt.Errorf("%s", errMessage)
}
+ return nil
}
// ValidateAbilityArgs validates the command line arguments
func ValidateAbilityArgs(args []string) error {
- handleHelpFlag(args)
-
- if len(args) > 4 {
- errMessage := styling.ErrorBorder.Render(styling.ErrorColor.Render("Error!"), "\nToo many arguments")
- return fmt.Errorf("%s", errMessage)
+ if err := checkLength(args, 4); err != nil {
+ return err
}
if len(args) == 2 {
@@ -36,11 +32,8 @@ func ValidateAbilityArgs(args []string) error {
// ValidateMoveArgs validates the command line arguments
func ValidateMoveArgs(args []string) error {
- handleHelpFlag(args)
-
- if len(args) > 4 {
- errMessage := styling.ErrorBorder.Render(styling.ErrorColor.Render("Error!"), "\nToo many arguments")
- return fmt.Errorf("%s", errMessage)
+ if err := checkLength(args, 3); err != nil {
+ return err
}
if len(args) == 2 {
@@ -53,21 +46,16 @@ func ValidateMoveArgs(args []string) error {
// ValidateNaturesArgs validates the command line arguments
func ValidateNaturesArgs(args []string) error {
- handleHelpFlag(args)
-
- if len(args) > 3 {
- errMessage := styling.ErrorBorder.Render(styling.ErrorColor.Render("Error!"), "\nToo many arguments")
- return fmt.Errorf("%s", errMessage)
+ if err := checkLength(args, 3); err != nil {
+ return err
}
// Check if there are exactly 3 arguments and the third argument is neither '-h' nor '--help'
// If true, return an error message since only '-h' and '--help' are allowed after 'types'
- if len(args) == 3 && (args[2] != "-h" && args[2] != "--help") {
- errorTitle := styling.ErrorColor.Render("Error!")
- errorString := "\nThe only currently available options\nafter [natures] command are '-h' or '--help'"
- finalErrorMessage := errorTitle + errorString
- renderedError := styling.ErrorBorder.Render(finalErrorMessage)
- return fmt.Errorf("%s", renderedError)
+ if len(args) == 3 && args[2] != "-h" && args[2] != "--help" {
+ errMsg := styling.ErrorColor.Render("Error!") +
+ "\nThe only currently available options\nafter command are '-h' or '--help'"
+ return fmt.Errorf("%s", styling.ErrorBorder.Render(errMsg))
}
return nil
@@ -75,8 +63,6 @@ func ValidateNaturesArgs(args []string) error {
// ValidatePokemonArgs validates the command line arguments
func ValidatePokemonArgs(args []string) error {
- handleHelpFlag(args)
-
// Check if the number of arguments is less than 3
if len(args) < 3 {
errMessage := styling.ErrorBorder.Render(
@@ -88,13 +74,8 @@ func ValidatePokemonArgs(args []string) error {
return fmt.Errorf("%s", errMessage)
}
- // Check if there are too many arguments
- if len(args) > 7 {
- errMessage := styling.ErrorBorder.Render(
- styling.ErrorColor.Render("Error!"),
- "\nToo many arguments",
- )
- return fmt.Errorf("%s", errMessage)
+ if err := checkLength(args, 7); err != nil {
+ return err
}
// Validate each argument after the Pokémon's name
@@ -131,19 +112,16 @@ func ValidatePokemonArgs(args []string) error {
// ValidateSearchArgs validates the command line arguments
func ValidateSearchArgs(args []string) error {
- if len(args) > 3 {
- errMessage := styling.ErrorBorder.Render(styling.ErrorColor.Render("Error!"), "\nToo many arguments")
- return fmt.Errorf("%s", errMessage)
+ if err := checkLength(args, 3); err != nil {
+ return err
}
// Check if there are exactly 3 arguments and the third argument is neither '-h' nor '--help'
- // If true, return an error message since only '-h' and '--help' are allowed after 'types'
- if len(args) == 3 && (args[2] != "-h" && args[2] != "--help") {
- errorTitle := styling.ErrorColor.Render("Error!")
- errorString := "\nThe only currently available options\nafter [search] command are '-h' or '--help'"
- finalErrorMessage := errorTitle + errorString
- renderedError := styling.ErrorBorder.Render(finalErrorMessage)
- return fmt.Errorf("%s", renderedError)
+ // If true, return an error message since only '-h' and '--help' are allowed after
+ if len(args) == 3 && args[2] != "-h" && args[2] != "--help" {
+ errMsg := styling.ErrorColor.Render("Error!") +
+ "\nThe only currently available options\nafter command are '-h' or '--help'"
+ return fmt.Errorf("%s", styling.ErrorBorder.Render(errMsg))
}
return nil
@@ -151,21 +129,16 @@ func ValidateSearchArgs(args []string) error {
// ValidateTypesArgs validates the command line arguments
func ValidateTypesArgs(args []string) error {
- handleHelpFlag(args)
-
- if len(args) > 3 {
- errMessage := styling.ErrorBorder.Render(styling.ErrorColor.Render("Error!"), "\nToo many arguments")
- return fmt.Errorf("%s", errMessage)
+ if err := checkLength(args, 3); err != nil {
+ return err
}
// Check if there are exactly 3 arguments and the third argument is neither '-h' nor '--help'
- // If true, return an error message since only '-h' and '--help' are allowed after 'types'
- if len(args) == 3 && (args[2] != "-h" && args[2] != "--help") {
- errorTitle := styling.ErrorColor.Render("Error!")
- errorString := "\nThe only currently available options\nafter [types] command are '-h' or '--help'"
- finalErrorMessage := errorTitle + errorString
- renderedError := styling.ErrorBorder.Render(finalErrorMessage)
- return fmt.Errorf("%s", renderedError)
+ // If true, return an error message since only '-h' and '--help' are allowed after
+ if len(args) == 3 && args[2] != "-h" && args[2] != "--help" {
+ errMsg := styling.ErrorColor.Render("Error!") +
+ "\nThe only currently available options\nafter command are '-h' or '--help'"
+ return fmt.Errorf("%s", styling.ErrorBorder.Render(errMsg))
}
return nil
diff --git a/cmd/validateargs_test.go b/cmd/validateargs_test.go
index 65d3e774..d1bca212 100644
--- a/cmd/validateargs_test.go
+++ b/cmd/validateargs_test.go
@@ -1,30 +1,63 @@
package cmd
import (
- "flag"
"github.com/digitalghost-dev/poke-cli/styling"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"testing"
)
-func TestHandleHelpFlag(t *testing.T) {
- // Mock flag.Usage to avoid actual printing
- flag.Usage = func() {}
-
- // Test cases
+func TestCheckLength(t *testing.T) {
+ // Define test cases
tests := []struct {
- name string
- args []string
+ name string
+ args []string
+ maxLength int
+ wantErr bool
+ expectedErr string
}{
- {"Valid short help flag", []string{"cmd", "subcmd", "-h"}},
- {"Valid long help flag", []string{"cmd", "subcmd", "--help"}},
- {"Invalid case (no flag)", []string{"cmd", "subcmd"}},
+ {
+ name: "Valid length - Empty slice",
+ args: []string{},
+ maxLength: 1,
+ wantErr: false,
+ expectedErr: "",
+ },
+ {
+ name: "Valid length - Within limit",
+ args: []string{"arg1", "arg2"},
+ maxLength: 3,
+ wantErr: false,
+ expectedErr: "",
+ },
+ {
+ name: "Valid length - Exactly at limit",
+ args: []string{"arg1", "arg2", "arg3"},
+ maxLength: 3,
+ wantErr: false,
+ expectedErr: "",
+ },
+ {
+ name: "Invalid length - Exceeds limit",
+ args: []string{"arg1", "arg2", "arg3", "arg4"},
+ maxLength: 3,
+ wantErr: true,
+ expectedErr: "Too many arguments",
+ },
}
- for _, tc := range tests {
- t.Run(tc.name, func(t *testing.T) {
- handleHelpFlag(tc.args)
+ // Run test cases
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ err := checkLength(tt.args, tt.maxLength)
+
+ // Check if an error was expected
+ if tt.wantErr {
+ require.Error(t, err)
+ assert.Contains(t, styling.StripANSI(err.Error()), tt.expectedErr)
+ } else {
+ assert.NoError(t, err)
+ }
})
}
}
@@ -107,7 +140,7 @@ func TestValidatePokemonArgs(t *testing.T) {
{"poke-cli", "pokemon", "dodrio", "-a", "-s", "-t"},
{"poke-cli", "pokemon", "dragalge", "-a", "-s", "-t", "--image=sm"},
{"poke-cli", "pokemon", "squirtle", "-a", "-s"},
- {"poke-cli", "pokemon", "squirtle", "-s", "-a"},
+ {"poke-cli", "pokemon", "dragapult", "-s", "-a"},
}
for _, input := range validInputs {
@@ -132,7 +165,7 @@ func TestValidatePokemonArgs(t *testing.T) {
// Testing too many arguments
tooManyArgs := [][]string{
- {"poke-cli", "pokemon", "hypno", "--abilities", "-s", "--types", "--image=sm", "-m"},
+ {"poke-cli", "pokemon", "hypo", "--abilities", "-s", "--types", "--image=sm", "-m"},
}
expectedError := styling.StripANSI("╭──────────────────╮\n│Error! │\n│Too many arguments│\n╰──────────────────╯")
diff --git a/go.mod b/go.mod
index 193f63b6..8855bc6e 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
module github.com/digitalghost-dev/poke-cli
-go 1.23.6
+go 1.24.1
require (
github.com/charmbracelet/bubbles v0.20.0