187 lines
7.2 KiB
Go
187 lines
7.2 KiB
Go
// Fenrir console screen reader.
|
|
// Copyright (c) 2026 Storm Dragon
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify it
|
|
// under the terms of the GNU General Public License as published by the Free
|
|
// Software Foundation, either version 3 of the License, or (at your option)
|
|
// any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful, but WITHOUT
|
|
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
// more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License along with
|
|
// this program. If not, see <https://www.gnu.org/licenses/gpl-3.0.html>.
|
|
|
|
package input
|
|
|
|
import (
|
|
"git.stormux.org/storm/fenrir/internal/command"
|
|
"testing"
|
|
)
|
|
|
|
func TestReviewAndReleaseOrder(t *testing.T) {
|
|
router := desktop_router(t)
|
|
if got := router.Key(1, KeyInsert, 1, false, false); got.Forward || got.Interrupt {
|
|
t.Fatalf("modifier: %+v", got)
|
|
}
|
|
if got := router.Key(2, KeyKP5, 1, false, false); got.Forward || got.Command != command.ReadScreen {
|
|
t.Fatalf("cross-device chord: %+v", got)
|
|
}
|
|
router.Key(1, KeyInsert, 0, false, false)
|
|
if got := router.Key(2, KeyKP5, 2, true, false); got.Forward || got.Command != "" {
|
|
t.Fatalf("held chord changed after modifier release: %+v", got)
|
|
}
|
|
if got := router.Key(2, KeyKP5, 0, true, false); got.Forward {
|
|
t.Fatalf("consumed release leaked: %+v", got)
|
|
}
|
|
if got := router.Key(2, KeyKP5, 1, false, false); got.Command != command.CurrentWord {
|
|
t.Fatalf("bare review: %+v", got)
|
|
}
|
|
if got := router.Key(2, KeyKP5, 2, false, false); got.Command != command.CurrentWord {
|
|
t.Fatalf("repeat: %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestTypingNumLockAndControl(t *testing.T) {
|
|
router := desktop_router(t)
|
|
if got := router.Key(1, KeyKP0, 1, true, false); !got.Forward || !got.Interrupt {
|
|
t.Fatalf("numeric zero must interrupt like other typing: %+v", got)
|
|
}
|
|
router.Key(1, KeyKP0, 0, true, false)
|
|
if got := router.Key(1, KeyKP1, 1, true, false); !got.Forward || !got.Interrupt || got.Command != "" {
|
|
t.Fatalf("numeric press: %+v", got)
|
|
}
|
|
if got := router.Key(1, KeyKP1, 0, false, false); !got.Forward || got.Interrupt {
|
|
t.Fatalf("numeric release after NumLock changed: %+v", got)
|
|
}
|
|
if got := router.Key(1, KeyCtrl, 1, false, false); !got.Forward || !got.Interrupt {
|
|
t.Fatalf("control must stop speech and reach application: %+v", got)
|
|
}
|
|
if got := router.Key(1, KeyKP4, 1, false, false); !got.Forward || got.Command != "" {
|
|
t.Fatalf("application control chord: %+v", got)
|
|
}
|
|
if got := router.Key(1, KeyShift, 1, false, false); !got.Forward || got.Interrupt {
|
|
t.Fatalf("shift must not interrupt: %+v", got)
|
|
}
|
|
if got := router.Key(1, 30, 1, false, false); !got.Forward || !got.Interrupt {
|
|
t.Fatalf("ordinary letter: %+v", got)
|
|
}
|
|
if got := router.Key(1, 30, 0, false, false); !got.Forward || got.Interrupt {
|
|
t.Fatalf("letter release: %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestPassNextChordAndReset(t *testing.T) {
|
|
router := desktop_router(t)
|
|
router.Pass_next()
|
|
if got := router.Key(1, KeyInsert, 1, false, false); !got.Forward {
|
|
t.Fatal("pass-next swallowed Insert")
|
|
}
|
|
if got := router.Key(1, KeyQ, 1, false, false); !got.Forward || got.Command != "" {
|
|
t.Fatalf("pass-next quit chord: %+v", got)
|
|
}
|
|
router.Key(1, KeyInsert, 0, false, false)
|
|
router.Key(1, KeyQ, 0, false, false)
|
|
router.Key(1, KeyInsert, 1, false, false)
|
|
if got := router.Key(1, KeyQ, 1, false, false); got.Forward || got.Command != command.Quit {
|
|
t.Fatalf("reader chord after pass-next: %+v", got)
|
|
}
|
|
router.Reset()
|
|
if got := router.Key(1, 30, 0, false, false); got.Forward {
|
|
t.Fatal("orphan release after handoff")
|
|
}
|
|
if got := router.Key(1, KeyKP8, 2, false, false); got.Command != "" {
|
|
t.Fatal("orphan repeat after handoff")
|
|
}
|
|
}
|
|
|
|
func TestSyncAndDisconnectDoNotRunCommands(t *testing.T) {
|
|
router := desktop_router(t)
|
|
router.Key(1, KeyInsert, 1, false, true)
|
|
if got := router.Key(1, KeyQ, 1, false, true); got.Forward || got.Command != "" || got.Interrupt {
|
|
t.Fatalf("sync ran command: %+v", got)
|
|
}
|
|
router.Drop(1)
|
|
if got := router.Key(2, KeyQ, 1, false, false); !got.Forward || got.Command != "" {
|
|
t.Fatalf("disconnected modifier stuck: %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestBareKeypadEnterIsConsumed(t *testing.T) {
|
|
router := desktop_router(t)
|
|
if got := router.Key(0, KeyKPEnter, 1, false, false); got.Command != "temp_disable_speech" || got.Forward || !got.Interrupt {
|
|
t.Fatal("keypad Enter did not temporarily silence speech", got)
|
|
}
|
|
if got := router.Key(0, KeyKPEnter, 2, false, false); got.Command != "" || got.Forward {
|
|
t.Fatal("held keypad Enter repeated or leaked", got)
|
|
}
|
|
if got := router.Key(0, KeyKPEnter, 0, true, false); got.Forward {
|
|
t.Fatal("keypad Enter release leaked after NumLock change", got)
|
|
}
|
|
if got := router.Key(0, KeyKPEnter, 1, true, false); !got.Forward || got.Command != "" {
|
|
t.Fatal("NumLock-on keypad Enter was consumed", got)
|
|
}
|
|
router.Reset()
|
|
router.Key(0, KeyInsert, 1, false, false)
|
|
if got := router.Key(0, KeyKPEnter, 1, false, false); got.Command != command.AutoRead || got.Forward {
|
|
t.Fatal("Fenrir+keypad Enter lost automatic-reading toggle", got)
|
|
}
|
|
}
|
|
|
|
func TestKeypressExcludesRepeatsReleasesAndStateRepair(t *testing.T) {
|
|
for _, key := range []uint16{KeyKPEnter, KeyKP8, KeyShift, KeyInsert, KeyF} {
|
|
for _, capture := range []bool{false, true} {
|
|
router := desktop_router(t)
|
|
router.Capture(capture)
|
|
if got := router.Key(0, key, 1, false, false); !got.Keypress {
|
|
t.Fatal("fresh press not reported", key, capture, got)
|
|
}
|
|
for _, event := range []struct {
|
|
value int32
|
|
sync bool
|
|
}{{2, false}, {1, false}, {0, false}, {1, true}, {0, true}, {2, false}} {
|
|
if got := router.Key(0, key, event.value, false, event.sync); got.Keypress {
|
|
t.Fatal("non-press reported as fresh key", key, capture, event, got)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTypingMetadataExcludesCommandsNavigationAndShortcuts(t *testing.T) {
|
|
for _, testCase := range []struct {
|
|
key uint16
|
|
modifier uint16
|
|
numLock, typing, boundary bool
|
|
}{
|
|
{30, 0, false, true, false}, {30, KeyShift, false, true, false},
|
|
{30, KeyRightAlt, false, true, false}, {30, KeyCtrl, false, false, false},
|
|
{30, KeyAlt, false, false, false}, {KeySpace, 0, false, true, true},
|
|
{KeyKP8, 0, false, false, false}, {KeyKP8, 0, true, true, false},
|
|
{KeyRight, 0, false, false, false}, {KeyBackspace, 0, false, false, false},
|
|
{KeyTab, 0, false, false, false}, {KeyF, KeyInsert, false, false, false},
|
|
} {
|
|
router := desktop_router(t)
|
|
if testCase.modifier != 0 {
|
|
router.Key(0, testCase.modifier, 1, testCase.numLock, false)
|
|
}
|
|
for _, value := range []int32{1, 2} {
|
|
got := router.Key(0, testCase.key, value, testCase.numLock, false)
|
|
if got.Typing != testCase.typing || got.WordBoundary != testCase.boundary {
|
|
t.Fatal("wrong typing classification", testCase, value, got)
|
|
}
|
|
}
|
|
for _, event := range []struct {
|
|
value int32
|
|
sync bool
|
|
}{{0, false}, {1, true}, {0, true}} {
|
|
if got := router.Key(0, testCase.key, event.value, testCase.numLock, event.sync); got.Typing || got.WordBoundary {
|
|
t.Fatal("release or state repair became typing", testCase, event, got)
|
|
}
|
|
}
|
|
}
|
|
}
|