Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion docs/src/content/docs/configuration/keybindings/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ The available arguments are:
| `RepoName` | The full name of the repo (e.g. `dlvhdr/gh-dash`) |
| `RepoPath` | The path to the Repo, using the `config.yml` `repoPaths` key to get the mapping |
| `PrNumber` | The PR number |
| `HeadRefName` | The PR's remote branch name |
| `HeadRefName` | The PR's head branch name |
| `BaseRefName` | The PR's base branch name |
| `Author` | The username of the PR author |

## Issue Keybindings

Expand All @@ -79,3 +81,4 @@ The available arguments are:
| `RepoName` | The full name of the repo (e.g. `dlvhdr/gh-dash`) |
| `RepoPath` | The path to the Repo, using the `config.yml` `repoPaths` key to get the mapping |
| `IssueNumber` | The issue number |
| `Author` | The username of the issue author |
1 change: 1 addition & 0 deletions internal/tui/modelUtils.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ func (m *Model) runCustomIssueCommand(commandTemplate string, issueData *data.Is
&map[string]any{
"RepoName": issueData.GetRepoNameWithOwner(),
"IssueNumber": issueData.Number,
"Author": issueData.Author.Login,
},
)
}
Expand Down
118 changes: 118 additions & 0 deletions internal/tui/ui_test.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package tui

import (
"bytes"
"fmt"
"io"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
"text/template"
"time"

tea "github.com/charmbracelet/bubbletea"
Expand Down Expand Up @@ -219,3 +221,119 @@ func TestNotificationView_PRViewTabNavigation(t *testing.T) {
require.NotEqual(t, currentTab, m.prView.SelectedTab(),
"prView tab should have changed after pressing prev tab key")
}

// executeCommandTemplate mimics the template execution logic from runCustomCommand
// to allow testing template variable substitution without executing shell commands.
func executeCommandTemplate(t *testing.T, commandTemplate string, input map[string]any) (string, error) {
t.Helper()
cmd, err := template.New("test_command").Parse(commandTemplate)
if err != nil {
return "", err
}
cmd = cmd.Option("missingkey=error")

var buff bytes.Buffer
err = cmd.Execute(&buff, input)
if err != nil {
return "", err
}
return buff.String(), nil
}

func TestPRCommandTemplateVariables(t *testing.T) {
// Test that PR command templates correctly substitute all available variables,
// matching the behavior of runCustomPRCommand in modelUtils.go
input := map[string]any{
"RepoName": "owner/repo",
"PrNumber": 123,
"HeadRefName": "feature-branch",
"BaseRefName": "main",
"Author": "testuser",
}

tests := []struct {
name string
template string
expected string
}{
{
name: "Author variable",
template: "gh pr view --author {{.Author}}",
expected: "gh pr view --author testuser",
},
{
name: "PrNumber variable",
template: "gh pr checkout {{.PrNumber}}",
expected: "gh pr checkout 123",
},
{
name: "HeadRefName variable",
template: "git checkout {{.HeadRefName}}",
expected: "git checkout feature-branch",
},
{
name: "Multiple variables",
template: "echo PR #{{.PrNumber}} by {{.Author}} in {{.RepoName}}: {{.HeadRefName}} -> {{.BaseRefName}}",
expected: "echo PR #123 by testuser in owner/repo: feature-branch -> main",
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
result, err := executeCommandTemplate(t, tc.template, input)
require.NoError(t, err)
require.Equal(t, tc.expected, result)
})
}
}

func TestIssueCommandTemplateVariables(t *testing.T) {
// Test that Issue command templates correctly substitute all available variables,
// matching the behavior of runCustomIssueCommand in modelUtils.go
input := map[string]any{
"RepoName": "owner/repo",
"IssueNumber": 456,
"Author": "issueauthor",
}

tests := []struct {
name string
template string
expected string
}{
{
name: "Author variable",
template: "gh issue view --author {{.Author}}",
expected: "gh issue view --author issueauthor",
},
{
name: "IssueNumber variable",
template: "gh issue view {{.IssueNumber}}",
expected: "gh issue view 456",
},
{
name: "Multiple variables",
template: "echo Issue #{{.IssueNumber}} by {{.Author}} in {{.RepoName}}",
expected: "echo Issue #456 by issueauthor in owner/repo",
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
result, err := executeCommandTemplate(t, tc.template, input)
require.NoError(t, err)
require.Equal(t, tc.expected, result)
})
}
}

func TestCommandTemplateMissingVariable(t *testing.T) {
// Test that templates with missing variables return an error,
// matching the missingkey=error behavior in runCustomCommand
input := map[string]any{
"RepoName": "owner/repo",
}

_, err := executeCommandTemplate(t, "gh pr view --author {{.Author}}", input)
require.Error(t, err, "template with missing variable should return an error")
}
Loading