190 lines
6.4 KiB
Go
190 lines
6.4 KiB
Go
// Copyright (c) 2026 Storm Dragon
|
|
// Authors: Storm Dragon, Angus Kola
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package input
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestIntentTracksModifiersAndExpires(t *testing.T) {
|
|
tracker := &IntentTracker{}
|
|
for _, step := range []struct {
|
|
name string
|
|
pressed bool
|
|
want Intent
|
|
}{
|
|
{"Down", true, LineIntent}, {"Down", false, LineIntent},
|
|
{"Right", true, CharacterIntent}, {"Control_L", true, OtherIntent},
|
|
{"Right", true, WordIntent}, {"Control_R", true, OtherIntent},
|
|
{"Control_L", false, OtherIntent}, {"Left", true, WordIntent},
|
|
{"Control_R", false, WordIntent}, {"Left", true, CharacterIntent},
|
|
{"s", true, TypingIntent}, {"period", true, TypingIntent},
|
|
{"BackSpace", true, DeleteIntent}, {"Return", true, OtherIntent},
|
|
{"Alt_L", true, OtherIntent}, {"Down", true, OtherIntent},
|
|
} {
|
|
tracker.Observe(NewKeyEvent(0, step.pressed, step.name))
|
|
if got := tracker.RecentIntent(); got != step.want {
|
|
t.Fatalf("%s pressed=%v: %v, want %v", step.name, step.pressed, got, step.want)
|
|
}
|
|
}
|
|
tracker.at = time.Now().Add(-2 * time.Second)
|
|
if tracker.RecentIntent() != NoIntent {
|
|
t.Fatal("stale input reused")
|
|
}
|
|
}
|
|
|
|
func TestIntentTracksClipboardChords(t *testing.T) {
|
|
tracker := &IntentTracker{}
|
|
press := func(name string) Activity {
|
|
tracker.Observe(NewKeyEvent(0, true, name))
|
|
return tracker.RecentInput()
|
|
}
|
|
press("Shift_L")
|
|
if got := press("Right"); got.Intent != CharacterIntent || !got.Selecting {
|
|
t.Fatalf("Shift+Right = %+v", got)
|
|
}
|
|
press("Control_L")
|
|
if got := press("Left"); got.Intent != WordIntent || !got.Selecting {
|
|
t.Fatalf("Ctrl+Shift+Left = %+v", got)
|
|
}
|
|
tracker.Observe(NewKeyEvent(0, false, "Shift_L"))
|
|
for _, tc := range []struct {
|
|
key string
|
|
want ClipboardAction
|
|
}{
|
|
{"c", CopyAction}, {"x", CutAction}, {"v", PasteAction},
|
|
} {
|
|
if got := press(tc.key); got.Clipboard != tc.want {
|
|
t.Fatalf("Ctrl+%s = %+v", tc.key, got)
|
|
}
|
|
}
|
|
if got := press("Right"); got.Clipboard != NoClipboardAction {
|
|
t.Fatalf("next key retained clipboard action: %+v", got)
|
|
}
|
|
tracker.Observe(NewKeyEvent(0, false, "Control_L"))
|
|
if got := press("v"); got.Clipboard != NoClipboardAction {
|
|
t.Fatalf("plain v = %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestPlainEnterActivityLifetime(t *testing.T) {
|
|
for _, key := range []string{"Return", "KP_Enter"} {
|
|
t.Run(key, func(t *testing.T) {
|
|
tracker := &IntentTracker{}
|
|
tracker.Observe(NewKeyEvent(0, true, key))
|
|
tracker.Observe(NewKeyEvent(0, false, key))
|
|
if activity := tracker.RecentInput(); !activity.PlainEnter || activity.Sequence == 0 || activity.Intent != OtherIntent {
|
|
t.Fatalf("Enter release lost activity: %+v", activity)
|
|
}
|
|
tracker.Observe(NewKeyEvent(0, true, "a"))
|
|
if tracker.RecentInput().PlainEnter {
|
|
t.Fatal("Enter survived the next key")
|
|
}
|
|
tracker.Observe(NewKeyEvent(0, true, key))
|
|
tracker.at = time.Now().Add(-2 * time.Second)
|
|
if tracker.RecentInput().PlainEnter {
|
|
t.Fatal("expired Enter remained active")
|
|
}
|
|
tracker.Observe(NewKeyEvent(0, true, key))
|
|
tracker.Reset()
|
|
if tracker.RecentInput().PlainEnter {
|
|
t.Fatal("reset retained Enter")
|
|
}
|
|
})
|
|
}
|
|
for _, modifier := range []string{"Shift_L", "Control_L", "Alt_L", "Super_L", "ISO_Level3_Shift"} {
|
|
t.Run(modifier, func(t *testing.T) {
|
|
tracker := &IntentTracker{}
|
|
tracker.Observe(NewKeyEvent(0, true, "Return"))
|
|
tracker.Observe(NewKeyEvent(0, true, modifier))
|
|
if tracker.RecentInput().PlainEnter {
|
|
t.Fatal("modifier press retained plain Enter")
|
|
}
|
|
tracker.Observe(NewKeyEvent(0, true, "Return"))
|
|
if tracker.RecentInput().PlainEnter {
|
|
t.Fatal("modified Enter classified as plain Enter")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSelectionIntentEndsWithPlainNavigation(t *testing.T) {
|
|
tracker := &IntentTracker{}
|
|
tracker.Observe(NewKeyEvent(0, true, "Shift_L"))
|
|
tracker.Observe(NewKeyEvent(0, true, "Down"))
|
|
if got := tracker.RecentInput(); !got.Selecting || got.Intent != LineIntent {
|
|
t.Fatalf("Shift+Down = %+v", got)
|
|
}
|
|
tracker.Observe(NewKeyEvent(0, false, "Shift_L"))
|
|
tracker.Observe(NewKeyEvent(0, true, "Right"))
|
|
if got := tracker.RecentInput(); got.Selecting || got.Intent != CharacterIntent {
|
|
t.Fatalf("Right = %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestTitleInterruptionTracksFreshPressesAndStopKeys(t *testing.T) {
|
|
tracker := &IntentTracker{}
|
|
for _, step := range []struct {
|
|
name string
|
|
pressed bool
|
|
repeat bool
|
|
want uint64
|
|
}{
|
|
{"a", true, false, 1}, {"a", false, false, 1},
|
|
{"a", true, true, 1}, {"Down", true, false, 2},
|
|
{"Down", true, true, 2}, {"Control_L", true, true, 3},
|
|
{"Control_R", true, true, 4}, {"Escape", true, true, 5},
|
|
} {
|
|
event := NewKeyEvent(0, step.pressed, step.name)
|
|
event.Repeat = step.repeat
|
|
tracker.Observe(event)
|
|
if got := tracker.RecentInput().TitleInterruptionSequence; got != step.want {
|
|
t.Fatalf("%+v: title interruption sequence = %d", step, got)
|
|
}
|
|
}
|
|
tracker.at = time.Now().Add(-2 * time.Second)
|
|
if got := tracker.RecentInput(); got.Intent != NoIntent || got.TitleInterruptionSequence != 5 {
|
|
t.Fatalf("expired activity = %+v", got)
|
|
}
|
|
tracker.Reset()
|
|
if got := tracker.RecentInput().TitleInterruptionSequence; got != 5 {
|
|
t.Fatalf("reset lost interruption history: %d", got)
|
|
}
|
|
}
|
|
|
|
func TestNavigationInputIdentitySurvivesIdle(t *testing.T) {
|
|
tracker := &IntentTracker{}
|
|
tracker.Observe(NewKeyEvent(0, true, "h"))
|
|
tracker.at = time.Now().Add(-2 * time.Second)
|
|
if got := tracker.RecentInput(); got.InputSequence != 1 || got.Sequence != 0 || got.Intent != NoIntent {
|
|
t.Fatalf("idle input identity = %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestPlainCharacterArrowsExcludeOtherNavigation(t *testing.T) {
|
|
for _, modifier := range []string{"", "Shift_L", "Control_L", "Alt_L", "Super_L", "ISO_Level3_Shift"} {
|
|
for _, key := range []string{"Left", "Right", "KP_Left", "Home", "End", "Up", "a"} {
|
|
var tracker IntentTracker
|
|
if modifier != "" {
|
|
tracker.Observe(NewKeyEvent(1, true, modifier))
|
|
}
|
|
tracker.Observe(NewKeyEvent(2, true, key))
|
|
want := modifier == "" && (key == "Left" || key == "Right" || key == "KP_Left")
|
|
if got := tracker.RecentInput().PlainCharacterArrow; got != want {
|
|
t.Fatalf("%s+%s hint = %t, want %t", modifier, key, got, want)
|
|
}
|
|
tracker.Observe(NewKeyEvent(2, false, key))
|
|
if tracker.RecentInput().PlainCharacterArrow != want {
|
|
t.Fatal("releasing arrow discarded presentation intent")
|
|
}
|
|
tracker.Reset()
|
|
if tracker.RecentInput().PlainCharacterArrow {
|
|
t.Fatal("reset retained phonetic arrow")
|
|
}
|
|
}
|
|
}
|
|
}
|