149 lines
4.7 KiB
Go
149 lines
4.7 KiB
Go
// Fenrir console screen reader.
|
|
// Copyright (c) 2026 Storm Dragon
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package reader
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
|
|
"git.stormux.org/storm/fenrir/internal/command"
|
|
"git.stormux.org/storm/fenrir/internal/presentation"
|
|
"git.stormux.org/storm/fenrir/internal/screen"
|
|
)
|
|
|
|
func dialog_snapshot(lines map[int]string, x, y int) screen.Snapshot {
|
|
snapshot := screen.Snapshot{Terminal: 1, Active: true, Rows: 25, Cols: 80, X: x, Y: y}
|
|
snapshot.Cells = make([]rune, snapshot.Rows*snapshot.Cols)
|
|
for index := range snapshot.Cells {
|
|
snapshot.Cells[index] = ' '
|
|
}
|
|
for row, text := range lines {
|
|
copy(snapshot.Line(row), []rune(text))
|
|
}
|
|
return snapshot
|
|
}
|
|
|
|
func TestDialogBodyWithUnchangedCursorRow(t *testing.T) {
|
|
// These are the two split redraws captured during the I38 audit: the
|
|
// button row is already present before the dialog body finishes drawing.
|
|
for _, testCase := range []struct {
|
|
name string
|
|
before, after screen.Snapshot
|
|
want []presentation.Message
|
|
}{
|
|
{
|
|
name: "saved preferences body",
|
|
before: dialog_snapshot(map[int]string{
|
|
8: "I38", 14: "< OK >",
|
|
}, 0, 19),
|
|
after: dialog_snapshot(map[int]string{
|
|
8: "I38", 9: "Loaded saved preferences from", 10: "/tmp/I38_preferences.conf",
|
|
12: "Missing or invalid values will be prompted.", 14: "< OK >",
|
|
}, 2, 14),
|
|
want: []presentation.Message{
|
|
{Text: "Loaded saved preferences from"}, {Text: "/tmp/I38_preferences.conf"},
|
|
{Text: "Missing or invalid values will be prompted."}, {Text: "< OK >"},
|
|
},
|
|
},
|
|
{
|
|
name: "last warning line",
|
|
before: dialog_snapshot(map[int]string{
|
|
0: "I38", 1: "I38Lock is not available with the current screen reader setup.",
|
|
2: "Requires i38lock and either Cthulhu or Orca 50 or newer.", 22: "< OK >",
|
|
}, 2, 3),
|
|
after: dialog_snapshot(map[int]string{
|
|
0: "I38", 1: "I38Lock is not available with the current screen reader setup.",
|
|
2: "Requires i38lock and either Cthulhu or Orca 50 or newer.",
|
|
3: "Please choose another screen lock method.", 22: "< OK >",
|
|
}, 2, 22),
|
|
want: []presentation.Message{
|
|
{Text: "Please choose another screen lock method."}, {Text: "< OK >"},
|
|
},
|
|
},
|
|
} {
|
|
t.Run(testCase.name, func(t *testing.T) {
|
|
r := New()
|
|
r.Update(testCase.before)
|
|
if got := r.Update(testCase.after); !reflect.DeepEqual(got, testCase.want) {
|
|
t.Fatalf("got %v, want %v", got, testCase.want)
|
|
}
|
|
if got := r.Update(testCase.after); len(got) != 0 {
|
|
t.Fatal("unchanged dialog repeated speech", got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCursorFeedbackPreservesConcurrentOutput(t *testing.T) {
|
|
for _, testCase := range []struct {
|
|
name string
|
|
before, after screen.Snapshot
|
|
want []presentation.Message
|
|
}{
|
|
{
|
|
"output below focus", fixture("first", "focus", ""), fixture("first", "focus", "new output"),
|
|
[]presentation.Message{{Text: "new output"}, {Text: "focus"}},
|
|
},
|
|
{
|
|
"scroll with fixed focus", fixture("old", "keep one", "keep two", "last", "focus"),
|
|
fixture("keep one", "keep two", "last", "new output", "focus"),
|
|
[]presentation.Message{{Text: "new output"}, {Text: "focus"}},
|
|
},
|
|
{
|
|
"changed focused row", fixture("first", "old focus"), fixture("first", "new focus"),
|
|
[]presentation.Message{{Text: "new"}},
|
|
},
|
|
{
|
|
"cleared body", fixture("old body", "focus"), fixture("", "focus"),
|
|
[]presentation.Message{{Text: "focus"}},
|
|
},
|
|
} {
|
|
t.Run(testCase.name, func(t *testing.T) {
|
|
testCase.after.Y = 1
|
|
if testCase.name == "scroll with fixed focus" {
|
|
testCase.after.Y = 4
|
|
}
|
|
if got := changes(testCase.before, testCase.after, true); !reflect.DeepEqual(got, testCase.want) {
|
|
t.Fatalf("got %v, want %v", got, testCase.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDialogRedrawRespectsReadingModes(t *testing.T) {
|
|
for _, mode := range []string{"cursor", "cursor off", "table", "highlight", "review", "automatic off"} {
|
|
t.Run(mode, func(t *testing.T) {
|
|
r := New()
|
|
before := fixture("I38", "", "< OK >")
|
|
r.Update(before)
|
|
switch mode {
|
|
case "cursor off":
|
|
r.Configure(true, false)
|
|
case "table", "highlight":
|
|
r.Configure_tracking(mode)
|
|
case "review":
|
|
r.Command(command.CurrentLine)
|
|
case "automatic off":
|
|
r.Configure(false, true)
|
|
}
|
|
after := fixture("I38", "New dialog body", "< OK >")
|
|
after.Y = 2
|
|
var want []presentation.Message
|
|
if mode != "automatic off" {
|
|
want = []presentation.Message{{Text: "New dialog body"}}
|
|
if mode == "cursor" {
|
|
want = append(want, presentation.Message{Text: "< OK >"})
|
|
}
|
|
}
|
|
if got := r.Update(after); !reflect.DeepEqual(got, want) {
|
|
t.Fatalf("got %v, want %v", got, want)
|
|
}
|
|
if mode == "review" && (!r.review.Active() || r.Command(command.CurrentLine).Text != "I38") {
|
|
t.Fatal("passive dialog redraw displaced review")
|
|
}
|
|
})
|
|
}
|
|
}
|