162 lines
5.0 KiB
Go
162 lines
5.0 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 interaction
|
|
|
|
import (
|
|
"math"
|
|
"testing"
|
|
)
|
|
|
|
func TestInvalidNumberUpDownStepRejected(t *testing.T) {
|
|
for _, step := range []float64{-1, math.NaN(), math.Inf(1), math.Inf(-1)} {
|
|
form := test_form()
|
|
form.Tabs[0].Controls[1].UpDownStep = step
|
|
if _, err := New_session(form); err == nil {
|
|
t.Fatalf("invalid Up/Down step %v accepted", step)
|
|
}
|
|
}
|
|
}
|
|
|
|
func test_form() Form {
|
|
return Form{Type: TabForm, Title: "Settings", Tabs: []Tab{
|
|
{ID: "speech", Label: "Speech", Controls: []Control{
|
|
{ID: "enabled", Label: "Speech enabled", Kind: CheckBox, Checked: true},
|
|
{ID: "range", Label: "Range", Kind: Number, Number: 2.5, Min: -5, Max: 5, Step: 0.1},
|
|
{ID: "text", Label: "Text", Kind: Entry, Text: "a界c"},
|
|
{ID: "ok", Label: "OK", Kind: Button, Action: "save"},
|
|
{ID: "cancel", Label: "Cancel", Kind: Button, Action: "cancel"},
|
|
}},
|
|
{ID: "sound", Label: "Sound"},
|
|
}}
|
|
}
|
|
|
|
func TestTabFormTraversalControlsAndIsolation(t *testing.T) {
|
|
form := test_form()
|
|
session, err := New_session(form)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if session.Open() != "Settings. Speech tab" {
|
|
t.Fatal(session.Open())
|
|
}
|
|
for _, name := range []string{"right", "left", "down", "up"} {
|
|
result := session.Handle(Key{Name: name})
|
|
if result.Speech == "" {
|
|
t.Fatal("silent tab", name)
|
|
}
|
|
}
|
|
if session.Describe() != "Speech tab" {
|
|
t.Fatal(session.Describe())
|
|
}
|
|
if got := session.Handle(Key{Name: "tab"}).Speech; got != "Speech enabled checked" {
|
|
t.Fatal(got)
|
|
}
|
|
if got := session.Handle(Key{Name: "space"}).Speech; got != "Speech enabled unchecked" {
|
|
t.Fatal(got)
|
|
}
|
|
if !form.Tabs[0].Controls[0].Checked {
|
|
t.Fatal("form mutated outside session")
|
|
}
|
|
session.Handle(Key{Name: "tab"})
|
|
for _, fixture := range []struct {
|
|
name string
|
|
want float64
|
|
}{
|
|
{"up", 3.5}, {"right", 3.6}, {"left", 3.5}, {"down", 2.5},
|
|
} {
|
|
session.Handle(Key{Name: fixture.name})
|
|
if got := session.Values()["speech.range"].Number; got != fixture.want {
|
|
t.Fatal(fixture, got)
|
|
}
|
|
}
|
|
for range 100 {
|
|
session.Handle(Key{Name: "up"})
|
|
}
|
|
if got := session.Values()["speech.range"].Number; got != 5 {
|
|
t.Fatal("upper bound", got)
|
|
}
|
|
for range 100 {
|
|
session.Handle(Key{Name: "down"})
|
|
}
|
|
if got := session.Values()["speech.range"].Number; got != -5 {
|
|
t.Fatal("lower bound", got)
|
|
}
|
|
session.Handle(Key{Name: "tab"})
|
|
session.Handle(Key{Name: "home"})
|
|
session.Handle(Key{Name: "right"})
|
|
session.Handle(Key{Name: "delete"})
|
|
session.Handle(Key{Text: "é"})
|
|
if got := session.Values()["speech.text"].Text; got != "aéc" {
|
|
t.Fatal("Unicode editing", got)
|
|
}
|
|
session.Handle(Key{Ctrl: true, Text: "a"})
|
|
session.Handle(Key{Text: "replacement"})
|
|
if got := session.Values()["speech.text"].Text; got != "replacement" {
|
|
t.Fatal("select all", got)
|
|
}
|
|
if got := session.Handle(Key{Name: "tab"}).Speech; got != "OK button" {
|
|
t.Fatal(got)
|
|
}
|
|
if got := session.Handle(Key{Name: "enter"}).Action; got != "save" {
|
|
t.Fatal(got)
|
|
}
|
|
if got := session.Handle(Key{Name: "tab"}).Speech; got != "Cancel button" {
|
|
t.Fatal(got)
|
|
}
|
|
if got := session.Handle(Key{Name: "enter"}).Action; got != "cancel" {
|
|
t.Fatal(got)
|
|
}
|
|
if got := session.Handle(Key{Name: "tab"}).Speech; got != "Speech tab" {
|
|
t.Fatal("tab cycle", got)
|
|
}
|
|
if got := session.Handle(Key{Name: "tab", Shift: true}).Speech; got != "Cancel button" {
|
|
t.Fatal("reverse cycle", got)
|
|
}
|
|
if !session.Handle(Key{Name: "escape"}).Cancelled {
|
|
t.Fatal("escape did not cancel")
|
|
}
|
|
}
|
|
|
|
func TestListCategoryFormReturnsSelectionWithoutSideEffects(t *testing.T) {
|
|
form := Form{Type: TabForm, Title: "Picker", Tabs: []Tab{{ID: "category", Label: "Category", Controls: []Control{{ID: "items", Label: "Items", Kind: List, Items: []string{"First", "Second"}, Action: "choose"}}}}}
|
|
session, err := New_session(form)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
session.Handle(Key{Name: "tab"})
|
|
if got := session.Handle(Key{Name: "up"}).Speech; got != "Second" {
|
|
t.Fatal(got)
|
|
}
|
|
if got := session.Handle(Key{Name: "enter"}).Action; got != "choose" {
|
|
t.Fatal(got)
|
|
}
|
|
values := session.Values()
|
|
values["category.items"].Items[1] = "changed"
|
|
if got := session.Describe(); got != "Items Second" {
|
|
t.Fatal("values aliased", got)
|
|
}
|
|
}
|
|
|
|
func TestUnknownFormTypeRejected(t *testing.T) {
|
|
form := test_form()
|
|
form.Type = "unknown"
|
|
if _, err := New_session(form); err == nil {
|
|
t.Fatal("unknown form type accepted")
|
|
}
|
|
}
|