Files
fenrir/internal/reader/attributes_test.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)
}
})
}
}