166 lines
5.3 KiB
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)
|
|
}
|
|
}
|
|
}
|