214 lines
6.5 KiB
Go
214 lines
6.5 KiB
Go
// Copyright (c) 2026 Storm Dragon
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package reader
|
|
|
|
import (
|
|
"git.stormux.org/storm/fenrir/internal/command"
|
|
"git.stormux.org/storm/fenrir/internal/presentation"
|
|
"git.stormux.org/storm/fenrir/internal/screen"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func attribute_fixture() screen.Snapshot {
|
|
s := fixture("alpha", "bravo", "third")
|
|
s.Attributes = make([]screen.Attribute, len(s.Cells))
|
|
for index := range s.Attributes {
|
|
s.Attributes[index] = screen.Attribute{Foreground: screen.ColorDefault, Background: screen.ColorDefault}
|
|
}
|
|
s.Attributes[s.Cols] = screen.Attribute{Foreground: screen.ColorIndexed | 1, Background: screen.ColorIndexed | 4, Style: screen.Bold | screen.Blink}
|
|
return s
|
|
}
|
|
|
|
func TestAttributeManualAndReviewOwnership(t *testing.T) {
|
|
r := New()
|
|
r.Configure_attributes("both", "fenrirFGColor fenrirBold fenrirBlink")
|
|
s := attribute_fixture()
|
|
r.Update(s)
|
|
if got := r.Command(command.AttributeCursor).Text; got != "default " || r.review.Active() {
|
|
t.Fatal(got, r.review.Active())
|
|
}
|
|
result := r.Command(command.NextLine)
|
|
if got := result.Attributes; got == nil || got.Cue != presentation.HasAttributes || got.Text != "red bold blink" {
|
|
t.Fatal(got)
|
|
}
|
|
for range 2 {
|
|
result := r.Command(command.AttributeCursor)
|
|
if got := result.Text; got != "red bold blink" || result.Attributes != nil {
|
|
t.Fatal(got, result.Attributes)
|
|
}
|
|
}
|
|
result = r.Command(command.CurrentLine)
|
|
if result.Attributes != nil {
|
|
t.Fatal("same position repeated")
|
|
}
|
|
s.Y = 2
|
|
r.Update(s) // Detached review must still describe row 1.
|
|
if got := r.Command(command.AttributeCursor).Text; got != "red bold blink" {
|
|
t.Fatal(got)
|
|
}
|
|
r.Command(command.FollowCursor)
|
|
if got := r.Command(command.AttributeCursor).Text; !strings.HasPrefix(got, "default") {
|
|
t.Fatal(got)
|
|
}
|
|
s.Attributes = nil
|
|
r.Update(s)
|
|
if got := r.Command(command.AttributeCursor).Text; got != "" {
|
|
t.Fatal("missing attributes invented", got)
|
|
}
|
|
}
|
|
|
|
func TestAutomaticAttributeNavigationStyleOnlyDefaultsAndModes(t *testing.T) {
|
|
for _, mode := range []string{"none", "speech", "beeps", "both"} {
|
|
t.Run(mode, func(t *testing.T) {
|
|
r := New()
|
|
r.Configure_attributes(mode, "fenrirFGColor fenrirBold fenrirBlink")
|
|
s := attribute_fixture()
|
|
r.Update(s)
|
|
r.Note_input(true, false, false)
|
|
s.Y = 1
|
|
output := r.Update(s)
|
|
feedback := attribute_messages(output)
|
|
if (len(feedback) == 1) != (mode != "none") {
|
|
t.Fatal(output)
|
|
}
|
|
if len(feedback) > 0 && ((feedback[0].Cue == presentation.HasAttributes) != (mode == "beeps" || mode == "both")) {
|
|
t.Fatal(feedback)
|
|
}
|
|
if got := attribute_messages(r.Update(s)); len(got) != 0 {
|
|
t.Fatal("repeat", got)
|
|
}
|
|
// Same cell/text/cursor, only its style changes, even after input expires.
|
|
r.applicationInputAt = time.Time{}
|
|
s.Attributes = append([]screen.Attribute(nil), s.Attributes...)
|
|
s.Attributes[s.Cols].Style = screen.Underline
|
|
if got := attribute_messages(r.Update(s)); (len(got) == 1) != (mode != "none") {
|
|
t.Fatal("style-only", got)
|
|
}
|
|
r.Note_input(true, false, false)
|
|
s.Y = 2
|
|
got := attribute_messages(r.Update(s))
|
|
if (len(got) == 1) != (mode == "speech" || mode == "both") {
|
|
t.Fatal("default", got)
|
|
}
|
|
if len(got) > 0 && got[0].Cue != "" {
|
|
t.Fatal("default presence cue", got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func attribute_messages(messages []presentation.Message) []presentation.Message {
|
|
var result []presentation.Message
|
|
for _, message := range messages {
|
|
if message.Attributes != nil {
|
|
result = append(result, message)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func TestAttributeSessionMissingDataAndSuppression(t *testing.T) {
|
|
for _, trial := range []string{"session", "inactive", "automatic", "cursor echo", "table", "highlight", "detached", "passive cursor", "missing"} {
|
|
t.Run(trial, func(t *testing.T) {
|
|
r := New()
|
|
r.Configure_attributes("both", "fenrirBold")
|
|
s := attribute_fixture()
|
|
r.Update(s)
|
|
r.Note_input(true, false, false)
|
|
s.Y = 1
|
|
switch trial {
|
|
case "session":
|
|
s.Generation++
|
|
case "inactive":
|
|
s.Active = false
|
|
case "automatic":
|
|
r.Configure(false, true)
|
|
case "cursor echo":
|
|
r.Configure(true, false)
|
|
case "table", "highlight":
|
|
r.Configure_tracking(trial)
|
|
case "detached":
|
|
r.Command(command.CurrentLine)
|
|
case "passive cursor":
|
|
r.applicationInputAt = time.Time{}
|
|
case "missing":
|
|
s.Attributes = nil
|
|
}
|
|
if got := attribute_messages(r.Update(s)); len(got) > 0 {
|
|
t.Fatal(got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAttributeEmptyFormatDoesNotClaimDefaultHasAttributes(t *testing.T) {
|
|
r := New()
|
|
r.Configure_attributes("both", "")
|
|
s := attribute_fixture()
|
|
r.Update(s)
|
|
r.Note_input(true, false, false)
|
|
s.Y = 1
|
|
if got := attribute_messages(r.Update(s)); len(got) != 1 || got[0].Text != "has attribute" {
|
|
t.Fatal(got)
|
|
}
|
|
r.Note_input(true, false, false)
|
|
s.Y = 2
|
|
if got := attribute_messages(r.Update(s)); len(got) != 0 {
|
|
t.Fatal(got)
|
|
}
|
|
}
|
|
|
|
func TestAttributeSessionToggleResumesModeAndKeepsConfiguredDefault(t *testing.T) {
|
|
for _, mode := range []string{"none", "speech", "beeps", "both"} {
|
|
t.Run(mode, func(t *testing.T) {
|
|
r := New()
|
|
r.Configure_attributes(mode, "fenrirBold")
|
|
s := attribute_fixture()
|
|
r.Update(s)
|
|
expected := "announcement of attributes disabled"
|
|
if mode == "none" {
|
|
expected = "announcement of attributes enabled"
|
|
}
|
|
if got := r.Command(command.ToggleAttributes).Text; got != expected {
|
|
t.Fatal(got)
|
|
}
|
|
r.Configure_attributes(mode, "fenrirBold") // Unrelated quick/menu activity.
|
|
result := r.Command(command.NextLine)
|
|
message := result.Attributes
|
|
if (message != nil) != (mode == "none") {
|
|
t.Fatal(mode, message)
|
|
}
|
|
if message != nil && message.Cue != presentation.HasAttributes {
|
|
t.Fatal(message)
|
|
}
|
|
if got := r.Command(command.AttributeCursor).Text; got != "bold" {
|
|
t.Fatal("manual report", got)
|
|
}
|
|
r.Command(command.ToggleAttributes)
|
|
r.Command(command.PreviousLine)
|
|
result = r.Command(command.NextLine)
|
|
message = result.Attributes
|
|
if (message != nil) != (mode != "none") {
|
|
t.Fatal("restored", mode, message)
|
|
}
|
|
if message != nil && message.Attributes.Speech != (mode != "beeps") {
|
|
t.Fatal("lost mode", mode, message)
|
|
}
|
|
// Changing the actual preference explicitly applies its new mode.
|
|
nextMode := "speech"
|
|
if mode == nextMode {
|
|
nextMode = "both"
|
|
}
|
|
r.Configure_attributes(nextMode, "fenrirBold")
|
|
r.Command(command.PreviousLine)
|
|
result = r.Command(command.NextLine)
|
|
if got := result.Attributes; got == nil || got.Text != "bold" || !got.Attributes.Speech {
|
|
t.Fatal(got)
|
|
}
|
|
})
|
|
}
|
|
}
|