Files
fenrir/internal/review/additional_actions_test.go

166 lines
5.3 KiB
Go

// Copyright (c) 2026 Storm Dragon
// SPDX-License-Identifier: GPL-3.0-or-later
package review
import (
"strings"
"testing"
"git.stormux.org/storm/fenrir/internal/command"
"git.stormux.org/storm/fenrir/internal/presentation"
"git.stormux.org/storm/fenrir/internal/screen"
)
func TestAdditionalReportsUseActiveCursorWithoutMovingIt(t *testing.T) {
for _, detached := range []bool{false, true} {
var r Review
s := fixture("top", " café Ω! ", "bottom")
s.X, s.Y = 7, 1
r.Update(s)
kind := " text cursor "
if detached {
r.Execute(command.CurrentChar)
r.Set_mark()
s.X, s.Y = 0, 2
r.Update(s)
kind = " review cursor "
}
for _, trial := range []struct {
id command.ID
text, after string
}{
{command.CursorLine, "2", kind + " line number"},
{command.CursorColumn, "8", kind + " column number"},
{command.LineToCursor, " café", kind},
{command.LineFromCursor, "Ω!", kind},
} {
result := r.Execute(trial.id)
if got := result.Text; got != trial.text || result.After.Text != trial.after || result.Cue != "" {
t.Fatalf("%s detached=%v: %q after=%q cue=%q", trial.id, detached, got, result.After.Text, result.Cue)
}
if r.x != 7 || r.y != 1 || r.Active() != detached || r.screen.X != s.X || r.screen.Y != s.Y {
t.Fatal("report moved or detached a cursor")
}
}
if detached && r.markCount != 1 {
t.Fatal("reports cleared marks")
}
// Attached reports must continue following subsequent application motion.
s.X, s.Y = 1, 0
r.Update(s)
want := "2"
if detached {
want = "8"
}
if got := r.Execute(command.CursorColumn).Text; got != want {
t.Fatal("wrong cursor after passive update", got)
}
r.Execute(command.FollowCursor)
if got := r.Execute(command.CursorLine).Text; got != "1" || r.Active() {
t.Fatal("follow cursor failed", got)
}
result := r.Execute(command.CurrentChar)
if result.After.Text != "" {
t.Fatal("stale cursor announcement")
}
}
}
func TestAdditionalLineRangesEdgesAndBlankCues(t *testing.T) {
for _, trial := range []struct {
line string
x int
before, after string
blankBefore, blankAfter bool
}{
{"ab界cd", 2, "ab", "界cd", false, false},
{"alpha", 0, "blank", "alpha", true, false},
{"alpha", 19, "alpha", "blank", false, true},
{"", 4, "blank", "blank", true, true},
{"blank", 0, "blank", "blank", true, false},
{strings.Repeat("z", 20), 19, strings.Repeat("z", 19), "z", false, false},
} {
var r Review
s := fixture(trial.line)
s.X = trial.x
r.Update(s)
for _, action := range []struct {
id command.ID
text string
blank bool
}{
{command.LineToCursor, trial.before, trial.blankBefore},
{command.LineFromCursor, trial.after, trial.blankAfter},
} {
result := r.Execute(action.id)
if got := result.Text; got != action.text || (result.Cue == presentation.Blank) != action.blank || result.After.Text != " text cursor " {
t.Fatalf("%s %q x=%d: %q cue=%q after=%q", action.id, trial.line, trial.x, got, result.Cue, result.After.Text)
}
}
}
}
func TestAdditionalScreenCharactersUsePhysicalCorners(t *testing.T) {
for _, lines := range [][]string{{"First row ends Z", " last"}, {"", ""}, {"Ω"}, {strings.Repeat("x", 19) + "É"}} {
var r Review
s := fixture(lines...)
r.Update(s)
r.Set_table_mode(true)
for _, trial := range []struct {
id command.ID
x, y int
label string
}{
{command.ScreenLastChar, 19, len(lines) - 1, "last character in screen"},
{command.ScreenFirstChar, 0, 0, "first character in screen"},
} {
char := s.Line(trial.y)[trial.x]
result := r.Execute(trial.id)
if got := result.Text; got != presentation.Character(char)+" "+trial.label || r.x != trial.x || r.y != trial.y {
t.Fatalf("%s: %q at %d,%d", trial.id, got, r.x, r.y)
}
if got := result.Character; got == nil || *got != char || !r.Active() || r.screen.X != 0 || r.screen.Y != 0 {
t.Fatal("wrong character metadata or cursor ownership")
}
}
}
}
func TestAdditionalReportsReadWholeTableRowAndResetWithSession(t *testing.T) {
var r Review
s := fixture("Name|Subject", "é|long word", "only")
s.X = 7
r.Update(s)
r.Execute(command.CurrentChar)
r.Set_mark()
r.Set_table_mode(true)
r.Execute(command.CurrentWord)
r.Set_mark()
if got := r.Execute(command.LineToCursor).Text; got != "Name|Su" {
t.Fatal("line report lost row scope", got)
}
if got := r.Execute(command.LineFromCursor).Text; got != "bject" || r.tableColumn != 1 || r.markCount != 1 {
t.Fatal("line report disturbed table state", got)
}
r.Execute(command.ReviewDown)
r.Execute(command.ReviewDown)
if got := r.Execute(command.LineToCursor).Text; got != "only" || r.tableColumn != 1 {
t.Fatal("missing cell changed whole-row report", got)
}
s.Generation++
s.X = 0
r.Update(s)
result := r.Execute(command.CursorColumn)
if got := result.Text; got != "1" || result.After.Text != " text cursor column number" {
t.Fatal("session kept detached cursor", got, result.After.Text)
}
r.Update(screen.Snapshot{})
for _, id := range []command.ID{command.CursorLine, command.CursorColumn, command.LineToCursor, command.LineFromCursor, command.ScreenFirstChar, command.ScreenLastChar} {
result := r.Execute(id)
if got := result.Text; got != "no text console" || result.Cue != presentation.Error || result.After.Text != "" {
t.Fatal("inactive screen", id, got)
}
}
}