Files
fenrir/internal/input/multi_press_test.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)
}
}
}
}