// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later package input import ( "reflect" "testing" ) func TestBindingDefaultsRoundTrip(t *testing.T) { for _, layout := range []KeyboardLayout{DesktopKeyboardLayout, LaptopKeyboardLayout} { for chord := range BrowseCommandBindings(layout) { parsed, err := ParseCommandChord(FormatCommandChord(chord)) if err != nil || parsed != chord { t.Fatalf("%+v round trip: %+v %v", chord, parsed, err) } } got, err := ResolveCommandBindings(layout, true, nil) if err != nil || !reflect.DeepEqual(got, BrowseCommandBindings(layout)) { t.Fatalf("default changed: %v", err) } } } func TestBindingOverridesScopesLayoutsAndDisable(t *testing.T) { overrides := []BindingOverride{ {DesktopKeyboardLayout, "global", Preferences, []string{"Cthulhu+Ctrl+F8", "Alt+F9"}}, {DesktopKeyboardLayout, "browse", DocumentNextHeading, []string{"F6"}}, {DesktopKeyboardLayout, "global", Quit, []string{}}, } if err := ValidateBindingOverrides(overrides); err != nil { t.Fatal(err) } for _, browse := range []bool{false, true} { bindings, err := ResolveCommandBindings(DesktopKeyboardLayout, browse, overrides) if err != nil { t.Fatal(err) } for _, key := range []string{"Cthulhu+Space", "Cthulhu+q"} { chord, _ := ParseCommandChord(key) if bindings[chord] != "" { t.Fatalf("old binding %s remains", key) } } chord, _ := ParseCommandChord("Alt+F9") if bindings[chord] != Preferences { t.Fatal("custom global missing") } if bindings[CommandChord{Symbol: 'h', CthulhuModifier: true}] != ToggleLearnMode { t.Fatal("learn mode shortcut lost") } chord, _ = ParseCommandChord("F6") if (bindings[chord] == DocumentNextHeading) != browse || bindings[CommandChord{Symbol: 'h'}] != "" { t.Fatal("browse scope leaked or old heading remains") } } laptop, _ := ResolveCommandBindings(LaptopKeyboardLayout, false, overrides) if !reflect.DeepEqual(laptop, DefaultCommandBindings(LaptopKeyboardLayout)) { t.Fatal("desktop override changed laptop") } if !reflect.DeepEqual(CloneBindingOverrides(overrides), overrides) { t.Fatal("clone changed overrides") } clone := CloneBindingOverrides(overrides) clone[0].Keys[0] = "F1" if overrides[0].Keys[0] == "F1" { t.Fatal("clone aliases keys") } } func TestBindingValidation(t *testing.T) { for _, overrides := range [][]BindingOverride{ {{DesktopKeyboardLayout, "global", Preferences, []string{"Cthulhu+q"}}}, {{DesktopKeyboardLayout, "global", Preferences, []string{"h"}}}, {{DesktopKeyboardLayout, "browse", DocumentNextHeading, []string{"Cthulhu+q"}}}, {{DesktopKeyboardLayout, "global", Preferences, []string{"F8", "f8"}}}, {{DesktopKeyboardLayout, "global", Preferences, []string{"Insert"}}}, {{DesktopKeyboardLayout, "global", Command("unknown"), []string{"F8"}}}, {{KeyboardLayout(0), "global", Preferences, []string{"F8"}}}, {{DesktopKeyboardLayout, "unknown", Preferences, []string{"F8"}}}, {{DesktopKeyboardLayout, "global", Preferences, nil}, {DesktopKeyboardLayout, "global", Preferences, nil}}, } { if err := ValidateBindingOverrides(overrides); err == nil { t.Fatalf("accepted %+v", overrides) } } for _, key := range []string{"", "Ctrl+", "Ctrl+Ctrl+x", "Cthulhu+Cthulhu+x", "Hyper+x", "F36", "Ctrl+\n"} { if _, err := ParseCommandChord(key); err == nil { t.Fatalf("accepted %q", key) } } swaps := []BindingOverride{{DesktopKeyboardLayout, "global", Quit, []string{"Cthulhu+Space"}}, {DesktopKeyboardLayout, "global", Preferences, []string{"Cthulhu+q"}}} if err := ValidateBindingOverrides(swaps); err != nil { t.Fatalf("swap: %v", err) } unbound := []BindingOverride{{DesktopKeyboardLayout, "global", PresentDate, []string{"F8"}}} if err := ValidateBindingOverrides(unbound); err != nil { t.Fatalf("unbound command: %v", err) } } func TestCustomBindingRouterBalancedReleaseAndMultiPress(t *testing.T) { overrides := []BindingOverride{{DesktopKeyboardLayout, "global", PresentTime, []string{"Cthulhu+Ctrl+Alt+F8"}}} bindings, err := ResolveCommandBindings(DesktopKeyboardLayout, true, overrides) if err != nil { t.Fatal(err) } router := NewCommandRouter([]uint32{0xff63}, bindings) for _, want := range []Command{PresentTime, PresentDate} { router.Handle(0xff63, 1, 0, true) consumed, command := router.Handle(0xffc5, 2, 4|8, true) if !consumed || command != want { t.Fatalf("%v %s want %s", consumed, command, want) } router.Handle(0xff63, 1, 0, false) if consumed, command = router.Handle(0xffc5, 2, 4|8, false); !consumed || command != "" { t.Fatal("custom release unbalanced") } } }