fix: handle capital keybindings from enhanced keyboard events (#322)
Fixes #286 Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
This commit is contained in:
@@ -9,8 +9,136 @@ import (
|
||||
|
||||
"github.com/bjarneo/cliamp/ipc"
|
||||
"github.com/bjarneo/cliamp/playlist"
|
||||
"github.com/bjarneo/cliamp/provider"
|
||||
)
|
||||
|
||||
type interactionBrowseProvider struct {
|
||||
commandsTestProvider
|
||||
}
|
||||
|
||||
func (p interactionBrowseProvider) Artists() ([]provider.ArtistInfo, error) { return nil, nil }
|
||||
|
||||
func (p interactionBrowseProvider) ArtistAlbums(string) ([]provider.AlbumInfo, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func keybindingTestModel() Model {
|
||||
local := commandsTestProvider{name: "Local"}
|
||||
return Model{
|
||||
playlist: playlist.New(),
|
||||
provider: local,
|
||||
providers: []ProviderEntry{
|
||||
{Key: "local", Name: "Local", Provider: local},
|
||||
{Key: "yt", Name: "YouTube", Provider: commandsTestProvider{name: "YouTube"}},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleKeyEnhancedShiftYSelectsYouTubeProvider(t *testing.T) {
|
||||
m := keybindingTestModel()
|
||||
msg := tea.KeyPressMsg{Code: 'y', ShiftedCode: 'Y', Mod: tea.ModShift}
|
||||
if got := msg.String(); got != "shift+y" {
|
||||
t.Fatalf("enhanced Shift+Y string = %q, want shift+y", got)
|
||||
}
|
||||
|
||||
m.handleKey(msg)
|
||||
|
||||
if got := m.provider.Name(); got != "YouTube" {
|
||||
t.Fatalf("active provider = %q, want YouTube", got)
|
||||
}
|
||||
if m.lyrics.visible {
|
||||
t.Fatal("lyrics.visible = true after enhanced Shift+Y, want false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleKeyEnhancedShiftNOpensProviderBrowser(t *testing.T) {
|
||||
browse := interactionBrowseProvider{commandsTestProvider{name: "Navidrome"}}
|
||||
m := keybindingTestModel()
|
||||
m.providers = append(m.providers, ProviderEntry{Key: "navidrome", Name: "Navidrome", Provider: browse})
|
||||
msg := tea.KeyPressMsg{Code: 'n', ShiftedCode: 'N', Mod: tea.ModShift}
|
||||
|
||||
m.handleKey(msg)
|
||||
|
||||
if !m.navBrowser.visible {
|
||||
t.Fatal("navBrowser.visible = false after enhanced Shift+N, want true")
|
||||
}
|
||||
if got := m.navBrowser.prov.Name(); got != "Navidrome" {
|
||||
t.Fatalf("browser provider = %q, want Navidrome", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleKeyEnhancedShiftLetterReachesActiveTextInput(t *testing.T) {
|
||||
m := keybindingTestModel()
|
||||
m.search.active = true
|
||||
|
||||
m.handleKey(tea.KeyPressMsg{Code: 'a', ShiftedCode: 'A', Mod: tea.ModShift})
|
||||
|
||||
if got := m.search.query; got != "A" {
|
||||
t.Fatalf("search query = %q, want A", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleKeyPreservesExistingLetterRepresentations(t *testing.T) {
|
||||
t.Run("lowercase toggles lyrics", func(t *testing.T) {
|
||||
m := keybindingTestModel()
|
||||
m.handleKey(tea.KeyPressMsg{Code: 'y', Text: "y"})
|
||||
|
||||
if !m.lyrics.visible {
|
||||
t.Fatal("lyrics.visible = false after lowercase y, want true")
|
||||
}
|
||||
if got := m.provider.Name(); got != "Local" {
|
||||
t.Fatalf("active provider = %q, want Local", got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("uppercase text selects provider", func(t *testing.T) {
|
||||
m := keybindingTestModel()
|
||||
msg := tea.KeyPressMsg{Code: 'y', ShiftedCode: 'Y', Text: "Y", Mod: tea.ModShift}
|
||||
if got := msg.String(); got != "Y" {
|
||||
t.Fatalf("uppercase text string = %q, want Y", got)
|
||||
}
|
||||
|
||||
m.handleKey(msg)
|
||||
|
||||
if got := m.provider.Name(); got != "YouTube" {
|
||||
t.Fatalf("active provider = %q, want YouTube", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestHandleKeyPreservesUnrelatedModifiedKeys(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
msg tea.KeyPressMsg
|
||||
want string
|
||||
}{
|
||||
{name: "shifted non-letter", msg: tea.KeyPressMsg{Code: '1', ShiftedCode: '!', Mod: tea.ModShift}, want: "shift+1"},
|
||||
{name: "control shifted letter", msg: tea.KeyPressMsg{Code: 'y', ShiftedCode: 'Y', Mod: tea.ModCtrl | tea.ModShift}, want: "ctrl+shift+y"},
|
||||
{name: "alt shifted letter", msg: tea.KeyPressMsg{Code: 'y', ShiftedCode: 'Y', Mod: tea.ModAlt | tea.ModShift}, want: "alt+shift+y"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
m := keybindingTestModel()
|
||||
if got := tt.msg.String(); got != tt.want {
|
||||
t.Fatalf("key string = %q, want %q", got, tt.want)
|
||||
}
|
||||
if got := normalizeShiftedLetter(tt.msg).String(); got != tt.want {
|
||||
t.Fatalf("normalized key string = %q, want %q", got, tt.want)
|
||||
}
|
||||
|
||||
m.handleKey(tt.msg)
|
||||
|
||||
if got := m.provider.Name(); got != "Local" {
|
||||
t.Fatalf("active provider = %q, want Local", got)
|
||||
}
|
||||
if m.lyrics.visible || m.navBrowser.visible {
|
||||
t.Fatalf("unexpected action: lyrics.visible=%v navBrowser.visible=%v", m.lyrics.visible, m.navBrowser.visible)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGlobalHelpOpensOverActiveTextInput(t *testing.T) {
|
||||
m := Model{search: searchState{active: true, query: "jazz"}}
|
||||
|
||||
|
||||
@@ -175,8 +175,20 @@ func (m *Model) providerToBottom() {
|
||||
m.providerMaybeAdjustScroll()
|
||||
}
|
||||
|
||||
func normalizeShiftedLetter(msg tea.KeyPressMsg) tea.KeyPressMsg {
|
||||
if msg.Text != "" || msg.Mod != tea.ModShift ||
|
||||
msg.Code < 'a' || msg.Code > 'z' ||
|
||||
msg.ShiftedCode < 'A' || msg.ShiftedCode > 'Z' {
|
||||
return msg
|
||||
}
|
||||
msg.Text = string(msg.ShiftedCode)
|
||||
return msg
|
||||
}
|
||||
|
||||
// handleKey processes a single key press and returns an optional command.
|
||||
func (m *Model) handleKey(msg tea.KeyPressMsg) tea.Cmd {
|
||||
msg = normalizeShiftedLetter(msg)
|
||||
|
||||
if msg.String() == "ctrl+c" {
|
||||
return m.quit()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user