Files

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")
}
}