refactor: better project structure (#35)
This project is starting to grow, and I've seen flaws in the initial project structure design I think I'll restructure the entire project directory for better scalability and also cleaner
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestComputeCursorCol(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
data []byte
|
||||
want int
|
||||
}{
|
||||
{
|
||||
name: "Simple prompt",
|
||||
data: []byte("λ "),
|
||||
want: 2,
|
||||
},
|
||||
{
|
||||
name: "ANSI color prompt",
|
||||
data: []byte("\033[32mλ \033[0m"),
|
||||
want: 2,
|
||||
},
|
||||
{
|
||||
name: "Carriage return and move right",
|
||||
data: []byte("λ \033[140G...22 chars right prompt...\r\033[2C"),
|
||||
want: 2,
|
||||
},
|
||||
{
|
||||
name: "OSC sequence before prompt",
|
||||
data: []byte("\033]0;iris on fix/menu-debouncing\007λ "),
|
||||
want: 2,
|
||||
},
|
||||
{
|
||||
name: "CSI Horizontal Absolute",
|
||||
data: []byte("abc\033[10Gde"),
|
||||
want: 11,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := ComputeCursorCol(tt.data)
|
||||
if got != tt.want {
|
||||
t.Errorf("ComputeCursorCol(%q) = %d, want %d", tt.data, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,10 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/versenilvis/iris/internal/config"
|
||||
"github.com/versenilvis/iris/internal/logger"
|
||||
"github.com/versenilvis/iris/spec"
|
||||
"github.com/versenilvis/iris/config"
|
||||
"github.com/versenilvis/iris/logger"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/versenilvis/iris/spec"
|
||||
)
|
||||
|
||||
func TestRenderGhostText_CursorAtEnd(t *testing.T) {
|
||||
o := NewOverlay()
|
||||
items := []spec.Suggestion{
|
||||
{Cmd: "git checkout -b feature"},
|
||||
}
|
||||
o.UpdateItems(items)
|
||||
|
||||
// case 1: cursor at end of buffer -> should render ghost text suffix
|
||||
out := o.RenderGhostText("git check", false, true)
|
||||
if !strings.Contains(out, "out -b feature") {
|
||||
t.Fatalf("Expected ghost text suffix 'out -b feature', got: %q", out)
|
||||
}
|
||||
if o.LastGhostLen == 0 {
|
||||
t.Fatalf("Expected LastGhostLen > 0, got %d", o.LastGhostLen)
|
||||
}
|
||||
|
||||
// case 2: cursor moved left (cursorAtEnd == false) -> should clear ghost text
|
||||
outClear := o.RenderGhostText("git check", false, false)
|
||||
if strings.Contains(outClear, "out -b feature") {
|
||||
t.Fatalf("Expected ghost text to be hidden/cleared when cursor moved left, got: %q", outClear)
|
||||
}
|
||||
if o.LastGhostLen != 0 {
|
||||
t.Fatalf("Expected LastGhostLen == 0 after clearing, got %d", o.LastGhostLen)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetGhostText(t *testing.T) {
|
||||
o := NewOverlay()
|
||||
items := []spec.Suggestion{
|
||||
{Cmd: "docker exec -it my-container bash"},
|
||||
}
|
||||
o.UpdateItems(items)
|
||||
|
||||
// case 1: cursor at end
|
||||
ghost := o.GetGhostText("docker e", true)
|
||||
expected := "xec -it my-container bash"
|
||||
if ghost != expected {
|
||||
t.Fatalf("Expected %q, got %q", expected, ghost)
|
||||
}
|
||||
|
||||
// case 2: cursor not at end (moved left)
|
||||
ghostLeft := o.GetGhostText("docker e", false)
|
||||
if ghostLeft != "" {
|
||||
t.Fatalf("Expected empty string when cursor not at end, got %q", ghostLeft)
|
||||
}
|
||||
|
||||
// case 3: user navigated menu with Up/Down arrow -> should sync with highlighted item
|
||||
o.SetUserNavigated(true)
|
||||
ghostNav := o.GetGhostText("docker e", true)
|
||||
if ghostNav != expected {
|
||||
t.Fatalf("Expected %q when user navigated menu, got %q", expected, ghostNav)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGhostText_MenuSync(t *testing.T) {
|
||||
o := NewOverlay()
|
||||
items := []spec.Suggestion{
|
||||
{Cmd: "git checkout -b first"},
|
||||
{Cmd: "git checkout master"},
|
||||
}
|
||||
o.UpdateItems(items)
|
||||
|
||||
// default item 0
|
||||
ghost0 := o.GetGhostText("git check", true)
|
||||
if ghost0 != "out -b first" {
|
||||
t.Fatalf("Expected 'out -b first', got %q", ghost0)
|
||||
}
|
||||
|
||||
// move cursor down to item 1
|
||||
o.MoveCursor("down")
|
||||
ghost1 := o.GetGhostText("git check", true)
|
||||
if ghost1 != "out master" {
|
||||
t.Fatalf("Expected 'out master', got %q", ghost1)
|
||||
}
|
||||
|
||||
out := o.RenderGhostText("git check", true, true)
|
||||
if !strings.Contains(out, "out master") {
|
||||
t.Fatalf("Expected RenderGhostText to render 'out master', got %q", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGhostText_Truncation(t *testing.T) {
|
||||
o := NewOverlay()
|
||||
longCmd := "git commit -m '" + strings.Repeat("a", 150) + "'"
|
||||
items := []spec.Suggestion{
|
||||
{Cmd: longCmd},
|
||||
}
|
||||
o.UpdateItems(items)
|
||||
o.SetPromptLen(10)
|
||||
|
||||
// typed query length 105 -> total cursor col = 115, default width = 120 -> available cols = 5
|
||||
typedQuery := "git commit -m '" + strings.Repeat("a", 90)
|
||||
out := o.RenderGhostText(typedQuery, false, true)
|
||||
if !strings.Contains(out, "…") {
|
||||
t.Fatalf("Expected truncated ghost text with '…', got %q", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHideMenu_PreservesTypedQueryForAI(t *testing.T) {
|
||||
o := NewOverlay()
|
||||
o.HideMenu("git commit")
|
||||
|
||||
if o.GetTypedQuery() != "git commit" {
|
||||
t.Fatalf("Expected TypedQuery to be preserved as 'git commit', got %q", o.GetTypedQuery())
|
||||
}
|
||||
|
||||
aiSugg := spec.Suggestion{
|
||||
Cmd: "git commit -m 'fix: test'",
|
||||
Desc: "AI suggestion",
|
||||
Source: "ai",
|
||||
Confidence: 85,
|
||||
}
|
||||
if !o.InjectAISuggestion(aiSugg) {
|
||||
t.Fatalf("Expected InjectAISuggestion to succeed after HideMenu")
|
||||
}
|
||||
if !o.IsVisible() || len(o.Items) == 0 || o.Items[0].Cmd != aiSugg.Cmd {
|
||||
t.Fatalf("Expected AI suggestion to be injected into Items[0] and Visible=true")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package shell
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestScanPosixAliases(t *testing.T) {
|
||||
|
||||
input := `
|
||||
alias gca='git commit -a'
|
||||
alias ta="tmux a -t" # this is a comment
|
||||
# alias hidden="not found"
|
||||
alias l='ls' ll='ls -l'
|
||||
`
|
||||
expected := map[string]string{
|
||||
"gca": "git commit -a",
|
||||
"ta": "tmux a -t",
|
||||
"l": "ls",
|
||||
"ll": "ls -l",
|
||||
}
|
||||
|
||||
got := ParseAliases(input)
|
||||
if !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("ScanPosixAliases() = %v; want %v", got, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitAliasTokens(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected []string
|
||||
}{
|
||||
|
||||
{"Single", "a='b'", []string{"a='b'"}},
|
||||
{"Multi", "a='b' c=\"d\"", []string{"a='b'", "c=\"d\""}},
|
||||
|
||||
{"With Space", "ta='tmux a -t' l='ls -l'", []string{"ta='tmux a -t'", "l='ls -l'"}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := SplitAliasTokens(tt.input)
|
||||
if !reflect.DeepEqual(got, tt.expected) {
|
||||
t.Errorf("SplitAliasTokens(%q) = %v; want %v", tt.input, got, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user