Fork channel

Create a new channel as a copy of main.

Rename channel

Rename main to:

Delete channel

Delete main? This cannot be undone.

classify_test.go
package tui

import (
	"testing"

	tea "charm.land/bubbletea/v2"

	"skraak/tools/calls"
)

func TestWrapText(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		maxWidth int
		want     string
	}{
		{
			name:     "short text unchanged",
			input:    "hello world",
			maxWidth: 20,
			want:     "hello world",
		},
		{
			name:     "wraps at word boundary",
			input:    "hello world this is a test",
			maxWidth: 10,
			want:     "hello\nworld this\nis a test",
		},
		{
			name:     "handles multiple lines",
			input:    "hello world\nfoo bar baz",
			maxWidth: 8,
			want:     "hello\nworld\nfoo bar\nbaz",
		},
		{
			name:     "empty string",
			input:    "",
			maxWidth: 10,
			want:     "",
		},
		{
			name:     "single long word",
			input:    "abcdefghijklmnopqrstuvwxyz",
			maxWidth: 10,
			want:     "abcdefghij\nklmnopqrstuvwxyz",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := wrapText(tt.input, tt.maxWidth)
			if got != tt.want {
				t.Errorf("wrapText() = %q, want %q", got, tt.want)
			}
		})
	}
}

func TestKeyRank(t *testing.T) {
	tests := []struct {
		key  string
		want int
	}{
		{"a", 0},
		{"z", 0},
		{"m", 0},
		{"A", 1},
		{"Z", 1},
		{"0", 2},
		{"9", 2},
		{"!", 3},
		{"@", 3},
		{"", 3},
		// Note: keyRank checks first char only, so "ab" ranks as 'a' (lowercase = 0)
		{"ab", 0},
	}

	for _, tt := range tests {
		t.Run(tt.key, func(t *testing.T) {
			if got := keyRank(tt.key); got != tt.want {
				t.Errorf("keyRank(%q) = %d, want %d", tt.key, got, tt.want)
			}
		})
	}
}

func TestBuildBindingsHelp(t *testing.T) {
	tests := []struct {
		name     string
		bindings []calls.KeyBinding
		want     string
	}{
		{
			name:     "empty bindings",
			bindings: nil,
			want:     "",
		},
		{
			name: "single binding species only",
			bindings: []calls.KeyBinding{
				{Key: "k", Species: "Kiwi"},
			},
			want: "k=Kiwi",
		},
		{
			name: "single binding with calltype",
			bindings: []calls.KeyBinding{
				{Key: "k", Species: "Kiwi", CallType: "Male"},
			},
			want: "k=Kiwi/Male",
		},
		{
			name: "multiple bindings sorted a-z then 0-9",
			bindings: []calls.KeyBinding{
				{Key: "9", Species: "Nine"},
				{Key: "a", Species: "Alpha"},
				{Key: "1", Species: "One"},
				{Key: "b", Species: "Beta"},
			},
			want: "a=Alpha  b=Beta  1=One  9=Nine",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := buildBindingsHelp(tt.bindings)
			if got != tt.want {
				t.Errorf("buildBindingsHelp() = %q, want %q", got, tt.want)
			}
		})
	}
}

func TestHandleCommentKeyCode(t *testing.T) {
	tests := []struct {
		name          string
		initialText   string
		initialCursor int
		keyCode       rune
		wantText      string
		wantCursor    int
		wantHandled   bool
	}{
		{
			name:          "left arrow moves cursor",
			initialText:   "hello",
			initialCursor: 3,
			keyCode:       tea.KeyLeft,
			wantText:      "hello",
			wantCursor:    2,
			wantHandled:   true,
		},
		{
			name:          "left arrow at start does nothing",
			initialText:   "hello",
			initialCursor: 0,
			keyCode:       tea.KeyLeft,
			wantText:      "hello",
			wantCursor:    0,
			wantHandled:   true,
		},
		{
			name:          "right arrow moves cursor",
			initialText:   "hello",
			initialCursor: 2,
			keyCode:       tea.KeyRight,
			wantText:      "hello",
			wantCursor:    3,
			wantHandled:   true,
		},
		{
			name:          "right arrow at end does nothing",
			initialText:   "hello",
			initialCursor: 5,
			keyCode:       tea.KeyRight,
			wantText:      "hello",
			wantCursor:    5,
			wantHandled:   true,
		},
		{
			name:          "backspace deletes char before cursor",
			initialText:   "hello",
			initialCursor: 3,
			keyCode:       tea.KeyBackspace,
			wantText:      "helo",
			wantCursor:    2,
			wantHandled:   true,
		},
		{
			name:          "backspace at start does nothing",
			initialText:   "hello",
			initialCursor: 0,
			keyCode:       tea.KeyBackspace,
			wantText:      "hello",
			wantCursor:    0,
			wantHandled:   true,
		},
		{
			name:          "delete removes char at cursor",
			initialText:   "hello",
			initialCursor: 2,
			keyCode:       tea.KeyDelete,
			wantText:      "helo",
			wantCursor:    2,
			wantHandled:   true,
		},
		{
			name:          "delete at end does nothing",
			initialText:   "hello",
			initialCursor: 5,
			keyCode:       tea.KeyDelete,
			wantText:      "hello",
			wantCursor:    5,
			wantHandled:   true,
		},
		{
			name:          "space inserts space",
			initialText:   "hello",
			initialCursor: 2,
			keyCode:       tea.KeySpace,
			wantText:      "he llo",
			wantCursor:    3,
			wantHandled:   true,
		},
		{
			name:          "space respects max length",
			initialText:   string(make([]byte, maxCommentLength)),
			initialCursor: 10,
			keyCode:       tea.KeySpace,
			wantText:      string(make([]byte, maxCommentLength)),
			wantCursor:    10,
			wantHandled:   true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			m := &Model{
				commentText:   tt.initialText,
				commentCursor: tt.initialCursor,
			}

			key := tea.Key{Code: tt.keyCode}
			gotHandled := m.handleCommentKeyCode(key)

			if gotHandled != tt.wantHandled {
				t.Errorf("handleCommentKeyCode() handled = %v, want %v", gotHandled, tt.wantHandled)
			}
			if m.commentText != tt.wantText {
				t.Errorf("commentText = %q, want %q", m.commentText, tt.wantText)
			}
			if m.commentCursor != tt.wantCursor {
				t.Errorf("commentCursor = %d, want %d", m.commentCursor, tt.wantCursor)
			}
		})
	}
}

func TestHandleCommentCtrl(t *testing.T) {
	tests := []struct {
		name          string
		initialText   string
		initialCursor int
		input         string
		wantText      string
		wantCursor    int
		wantHandled   bool
	}{
		{
			name:          "ctrl+u clears text",
			initialText:   "hello world",
			initialCursor: 5,
			input:         "ctrl+u",
			wantText:      "",
			wantCursor:    0,
			wantHandled:   true,
		},
		{
			name:          "ctrl+a moves to start",
			initialText:   "hello",
			initialCursor: 3,
			input:         "ctrl+a",
			wantText:      "hello",
			wantCursor:    0,
			wantHandled:   true,
		},
		{
			name:          "ctrl+e moves to end",
			initialText:   "hello",
			initialCursor: 2,
			input:         "ctrl+e",
			wantText:      "hello",
			wantCursor:    5,
			wantHandled:   true,
		},
		{
			name:          "unknown ctrl combo not handled",
			initialText:   "hello",
			initialCursor: 2,
			input:         "ctrl+x",
			wantText:      "hello",
			wantCursor:    2,
			wantHandled:   false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			m := &Model{
				commentText:   tt.initialText,
				commentCursor: tt.initialCursor,
			}

			gotHandled := m.handleCommentCtrl(tt.input)

			if gotHandled != tt.wantHandled {
				t.Errorf("handleCommentCtrl() handled = %v, want %v", gotHandled, tt.wantHandled)
			}
			if m.commentText != tt.wantText {
				t.Errorf("commentText = %q, want %q", m.commentText, tt.wantText)
			}
			if m.commentCursor != tt.wantCursor {
				t.Errorf("commentCursor = %d, want %d", m.commentCursor, tt.wantCursor)
			}
		})
	}
}

func TestMaxCommentLength(t *testing.T) {
	if maxCommentLength != 140 {
		t.Errorf("maxCommentLength = %d, want 140", maxCommentLength)
	}
}

func TestMaxClipPrefixLength(t *testing.T) {
	if maxClipPrefixLength != 64 {
		t.Errorf("maxClipPrefixLength = %d, want 64", maxClipPrefixLength)
	}
}