188 lines
6.4 KiB
Go
188 lines
6.4 KiB
Go
// Copyright (c) 2026 Storm Dragon
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package input
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"git.stormux.org/storm/fenrir/internal/command"
|
|
)
|
|
|
|
func TestTriplePressSequences(t *testing.T) {
|
|
for _, layout := range []string{"desktop", "laptop"} {
|
|
for _, modifierFirst := range []bool{false, true} {
|
|
t.Run(layout+"/"+map[bool]string{false: "key first", true: "modifier first"}[modifierFirst], func(t *testing.T) {
|
|
bindings, _, err := Load_bindings([]string{"../../config/keyboard/" + layout + ".toml"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
primary := uint16(KeyKP5)
|
|
target := command.ReadScreen
|
|
if layout == "laptop" {
|
|
primary = 37
|
|
target = command.CurrentWord
|
|
}
|
|
for index := range bindings.Bindings {
|
|
if bindings.Bindings[index].Command == target {
|
|
bindings.Bindings[index].Command = command.CurrentWord
|
|
bindings.Bindings[index].DoubleCommand = command.CurrentChar
|
|
bindings.Bindings[index].TripleCommand = command.CurrentLine
|
|
}
|
|
}
|
|
router, err := New_router(bindings)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := router.Set_multi_press_timeout(700 * time.Millisecond); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
now := time.Unix(1000, 0)
|
|
router.now = func() time.Time { return now }
|
|
for _, want := range []command.ID{command.CurrentWord, command.CurrentChar, command.CurrentLine, command.CurrentWord} {
|
|
router.Key(1, KeyInsert, 1, false, false)
|
|
got := router.Key(1, primary, 1, false, false)
|
|
if got.Command != want || got.Forward || !got.Keypress || !got.Interrupt {
|
|
t.Fatal(got, want)
|
|
}
|
|
// Both evdev repeats and duplicate X11 presses stay in the held action.
|
|
for _, value := range []int32{2, 1, 2} {
|
|
got := router.Key(1, primary, value, false, false)
|
|
expected := command.ID("")
|
|
if value == 2 {
|
|
expected = want
|
|
}
|
|
if got.Command != expected || got.Keypress || got.Forward {
|
|
t.Fatal("held", got, want)
|
|
}
|
|
}
|
|
releases := []uint16{primary, KeyInsert}
|
|
if modifierFirst {
|
|
releases[0], releases[1] = releases[1], releases[0]
|
|
}
|
|
for _, key := range releases {
|
|
if got := router.Key(1, key, 0, false, false); got.Forward || got.Command != "" {
|
|
t.Fatal("release", got)
|
|
}
|
|
}
|
|
// Total triple duration exceeds the window; each adjacent gap fits.
|
|
now = now.Add(600 * time.Millisecond)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTriplePressBoundariesAndInvalidation(t *testing.T) {
|
|
for _, scenario := range []string{"inside", "at boundary", "outside", "backward clock", "other chord", "typing", "numlock", "reset", "capture", "drop", "repair", "unchanged repair", "other device", "pass next", "reconfigure"} {
|
|
t.Run(scenario, func(t *testing.T) {
|
|
router, err := New_router(Layout{Bindings: []Binding{{Name: "Review", Keys: []string{"KP5"}, Command: command.CurrentWord, DoubleCommand: command.CurrentChar, TripleCommand: command.CurrentLine}, {Name: "Other", Keys: []string{"FENRIR", "KP5"}, Command: command.Time, DoubleCommand: command.Date}}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
now := time.Unix(1000, 0)
|
|
router.now = func() time.Time { return now }
|
|
for _, want := range []command.ID{command.CurrentWord, command.CurrentChar} {
|
|
if got := router.Key(1, KeyKP5, 1, false, false); got.Command != want {
|
|
t.Fatal(got)
|
|
}
|
|
router.Key(1, KeyKP5, 0, false, false)
|
|
now = now.Add(100 * time.Millisecond)
|
|
}
|
|
want, device := command.CurrentWord, 1
|
|
switch scenario {
|
|
case "inside":
|
|
want = command.CurrentLine
|
|
case "at boundary":
|
|
now = now.Add(100 * time.Millisecond)
|
|
want = command.CurrentLine
|
|
case "outside":
|
|
now = now.Add(100*time.Millisecond + time.Nanosecond)
|
|
case "backward clock":
|
|
now = now.Add(-101 * time.Millisecond)
|
|
case "other chord":
|
|
router.Key(1, KeyInsert, 1, false, false)
|
|
router.Key(1, KeyKP5, 1, false, false)
|
|
router.Key(1, KeyKP5, 0, false, false)
|
|
router.Key(1, KeyInsert, 0, false, false)
|
|
case "typing":
|
|
if got := router.Key(1, 30, 1, false, false); !got.Typing || !got.Forward {
|
|
t.Fatal(got)
|
|
}
|
|
router.Key(1, 30, 0, false, false)
|
|
case "numlock":
|
|
if got := router.Key(1, KeyKP5, 1, true, false); !got.Forward {
|
|
t.Fatal(got)
|
|
}
|
|
router.Key(1, KeyKP5, 0, true, false)
|
|
case "reset":
|
|
router.Reset()
|
|
case "capture":
|
|
router.Capture(true)
|
|
if got := router.Key(1, KeyKP5, 1, false, false); got.UI == nil || got.Command != "" || got.Forward {
|
|
t.Fatal(got)
|
|
}
|
|
router.Key(1, KeyKP5, 0, false, false)
|
|
router.Capture(false)
|
|
case "drop":
|
|
router.Drop(1)
|
|
case "repair":
|
|
router.Key(1, KeyShift, 1, false, true)
|
|
router.Key(1, KeyShift, 0, false, true)
|
|
case "unchanged repair":
|
|
router.Key(1, KeyShift, 0, false, true)
|
|
want = command.CurrentLine
|
|
case "other device":
|
|
device = 2
|
|
case "pass next":
|
|
router.Pass_next()
|
|
if got := router.Key(1, KeyKP5, 1, false, false); !got.Forward || got.Command != "" {
|
|
t.Fatal(got)
|
|
}
|
|
router.Key(1, KeyKP5, 0, false, false)
|
|
case "reconfigure":
|
|
if err := router.Set_multi_press_timeout(500 * time.Millisecond); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if got := router.Key(device, KeyKP5, 1, false, false); got.Command != want || got.Forward {
|
|
t.Fatal(got, want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTripleBindingValidation(t *testing.T) {
|
|
for _, data := range []string{
|
|
"version=1\n[[bindings]]\nname='Review'\nkeys=['KP5']\ncommand='current_word'\ntriple_command='current_line'\n",
|
|
"version=1\n[[bindings]]\nname='Review'\nkeys=['KP5']\ncommand='current_word'\ndouble_command='current_character'\ntriple_command='unknown'\n",
|
|
} {
|
|
if _, err := Decode_bindings([]byte(data)); err == nil {
|
|
t.Fatal("invalid triple accepted", data)
|
|
}
|
|
}
|
|
router := desktop_router(t)
|
|
for _, timeout := range []time.Duration{0, -time.Second, time.Millisecond - time.Nanosecond, 5*time.Second + time.Nanosecond} {
|
|
if err := router.Set_multi_press_timeout(timeout); err == nil {
|
|
t.Fatal(timeout)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRetainedFeatureKeyNames(t *testing.T) {
|
|
for _, key := range []string{"F1", "KPMINUS", "KPSLASH", "RIGHTBRACE"} {
|
|
for _, bare := range []bool{false, true} {
|
|
keys := []string{"FENRIR", key}
|
|
if bare {
|
|
keys = keys[1:]
|
|
}
|
|
_, err := New_router(Layout{Bindings: []Binding{{Name: "Retained key", Keys: keys, Command: command.Help}}})
|
|
wantValid := !bare || key == "KPMINUS" || key == "KPSLASH"
|
|
if (err == nil) != wantValid {
|
|
t.Fatal(key, bare, err)
|
|
}
|
|
}
|
|
}
|
|
}
|