Files
cthulhu/internal/input/flat_review_test.go

186 lines
6.9 KiB
Go

// Cthulhu screen reader.
// Copyright (c) 2026 Storm Dragon
// Authors: Storm Dragon, Angus Kola
//
// 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 (
"testing"
"time"
)
func TestReviewMultiPressSequences(t *testing.T) {
for _, layout := range []KeyboardLayout{DesktopKeyboardLayout, LaptopKeyboardLayout} {
for _, modifierFirst := range []bool{false, true} {
router := NewCommandRouter([]uint32{0xff63}, DefaultCommandBindings(layout))
now := time.Unix(100, 0)
router.now = func() time.Time { return now }
wordKey, charKey := uint32(0xff9d), uint32(0xff99)
if layout == LaptopKeyboardLayout {
wordKey, charKey = 'k', ','
}
press := func(symbol uint32, want Command) {
t.Helper()
if layout == LaptopKeyboardLayout {
router.Handle(0xff63, 118, 0, true)
}
if consumed, got := router.Handle(symbol, 30, 0, true); !consumed || got != want {
t.Fatalf("layout=%d press=%x got=%s want=%s", layout, symbol, got, want)
}
if consumed, got := router.Handle(symbol, 30, 0, true); !consumed || got != "" {
t.Fatal("held-key repeat dispatched")
}
if layout == LaptopKeyboardLayout && modifierFirst {
router.Handle(0xff63, 118, 0, false)
}
if consumed, got := router.Handle(symbol, 30, 0, false); !consumed || got != "" {
t.Fatal("unbalanced release")
}
if layout == LaptopKeyboardLayout && !modifierFirst {
router.Handle(0xff63, 118, 0, false)
}
router.Reset()
router.ReplaceBindings(BrowseCommandBindings(layout))
}
press(charKey, ReviewCurrentCharacter)
now = now.Add(500 * time.Millisecond)
press(charKey, ReviewPhoneticCharacter)
press(charKey, ReviewCurrentCharacter)
press(wordKey, ReviewCurrentWord) // Different chord resets the sequence.
press(wordKey, ReviewSpellWord)
press(wordKey, ReviewPhoneticWord)
press(wordKey, ReviewCurrentWord)
now = now.Add(501 * time.Millisecond)
press(wordKey, ReviewCurrentWord)
router.Handle(0xffbe, 67, 0, true) // Unbound intervening key.
router.Handle(0xffbe, 67, 0, false)
press(wordKey, ReviewCurrentWord)
now = now.Add(-time.Millisecond)
press(wordKey, ReviewCurrentWord) // Clock reversal must not count.
}
}
}
func TestFlatReviewLayoutBindingsAndBalancedRelease(t *testing.T) {
for _, tc := range []struct {
desktop, laptop uint32
command Command
}{
{0xff95, 'u', ReviewPreviousLine}, {0xff97, 'i', ReviewCurrentLine}, {0xff9a, 'o', ReviewNextLine},
{0xff96, 'j', ReviewPreviousWord}, {0xff9d, 'k', ReviewCurrentWord}, {0xff98, 'l', ReviewNextWord},
{0xff9c, 'm', ReviewPreviousCharacter}, {0xff99, ',', ReviewCurrentCharacter}, {0xff9b, '.', ReviewNextCharacter},
} {
for _, layout := range []KeyboardLayout{DesktopKeyboardLayout, LaptopKeyboardLayout} {
for _, modifierFirst := range []bool{false, true} {
router := NewCommandRouter([]uint32{0xff63}, DefaultCommandBindings(layout))
symbol := tc.desktop
if layout == LaptopKeyboardLayout {
symbol = tc.laptop
if consumed, _ := router.Handle(symbol, 30, 0, true); consumed {
t.Fatal("unmodified laptop letter consumed")
}
router.Handle(symbol, 30, 0, false)
router.Handle(0xff63, 118, 0, true)
}
if consumed, cmd := router.Handle(symbol, 30, 0, true); !consumed || cmd != tc.command {
t.Fatalf("press: %v %q", consumed, cmd)
}
if consumed, cmd := router.Handle(symbol, 30, 0, true); !consumed || cmd != "" {
t.Fatalf("repeat: %v %q", consumed, cmd)
}
if layout == LaptopKeyboardLayout && modifierFirst {
router.Handle(0xff63, 118, 0, false)
}
consumed, cmd := router.Handle(symbol, 30, 0, false)
if !consumed {
t.Fatal("review release leaked")
}
if layout == LaptopKeyboardLayout && !modifierFirst {
if cmd != "" {
t.Fatal("command emitted while Insert held")
}
_, cmd = router.Handle(0xff63, 118, 0, false)
}
if cmd != "" {
t.Fatalf("release dispatched %q", cmd)
}
}
}
}
}
func TestFlatReviewWindowBindingsAndBalancedRelease(t *testing.T) {
for _, tc := range []struct {
symbol uint32
layout KeyboardLayout
modifiers uint16
command Command
}{
{0xff95, DesktopKeyboardLayout, 0, ReviewWindowStart},
{0xff9a, DesktopKeyboardLayout, 0, ReviewWindowEnd},
{'u', LaptopKeyboardLayout, controlModifier, ReviewWindowStart},
{'o', LaptopKeyboardLayout, controlModifier, ReviewWindowEnd},
} {
for _, releaseModifierFirst := range []bool{false, true} {
router := NewCommandRouter([]uint32{0xff63}, DefaultCommandBindings(tc.layout))
router.Handle(0xff63, 118, 0, true)
if consumed, command := router.Handle(tc.symbol, 30, tc.modifiers, true); !consumed || command != tc.command {
t.Fatalf("press: consumed=%v command=%q", consumed, command)
}
releases := []struct {
symbol uint32
code uint16
}{{tc.symbol, 30}, {0xff63, 118}}
if releaseModifierFirst {
releases[0], releases[1] = releases[1], releases[0]
}
for _, key := range releases {
if consumed, command := router.Handle(key.symbol, key.code, 0, false); !consumed || command != "" {
t.Fatalf("release: consumed=%v command=%q", consumed, command)
}
}
}
}
}
func TestFlatReviewVerticalAndLineEndBindings(t *testing.T) {
for _, tc := range []struct {
symbol uint32
layout KeyboardLayout
modifiers uint16
command Command
}{
{0xff96, DesktopKeyboardLayout, 0, ReviewWordAbove},
{0xff98, DesktopKeyboardLayout, 0, ReviewWordBelow},
{0xff9c, DesktopKeyboardLayout, 0, ReviewLineEnd},
{'j', LaptopKeyboardLayout, controlModifier, ReviewWordAbove},
{'l', LaptopKeyboardLayout, controlModifier, ReviewWordBelow},
{'m', LaptopKeyboardLayout, controlModifier, ReviewLineEnd},
} {
router := NewCommandRouter([]uint32{0xff63}, DefaultCommandBindings(tc.layout))
router.Handle(0xff63, 118, 0, true)
if consumed, command := router.Handle(tc.symbol, 30, tc.modifiers, true); !consumed || command != tc.command {
t.Fatalf("press: consumed=%v command=%q want=%q", consumed, command, tc.command)
}
if consumed, command := router.Handle(tc.symbol, 30, 0, false); !consumed || command != "" {
t.Fatalf("release: consumed=%v command=%q", consumed, command)
}
if consumed, command := router.Handle(0xff63, 118, 0, false); !consumed || command != "" {
t.Fatalf("modifier release: consumed=%v command=%q", consumed, command)
}
}
}