After commit c018f5fd29 Fenrir is being switched over to go. If you do not want to be part of the testing process, please switch to master.

This commit is contained in:
Storm Dragon committed 2026-10-27 20:30:55 -04:00
1 parent c018f5fd29
commit 0e50de4c76
1047 files changed
+58364 -63747

No files matched your search

+213
View File
@@ -0,0 +1,213 @@
// Copyright (c) 2026 Storm Dragon
// SPDX-License-Identifier: GPL-3.0-or-later
package review
import (
"git.stormux.org/storm/fenrir/internal/command"
"git.stormux.org/storm/fenrir/internal/presentation"
"git.stormux.org/storm/fenrir/internal/screen"
"testing"
)
func TestCharacterCrossesPhysicalRowsAndKeepsCharacterMetadata(t *testing.T) {
for _, feedback := range []bool{false, true} {
var r Review
r.Configure(feedback, feedback, true)
s := screen.Snapshot{Active: true, Rows: 3, Cols: 3, Cells: []rune("abÉ 界cd"), X: 2}
r.Update(s)
for _, trial := range []struct {
id command.ID
text string
x, y int
crossing bool
}{
{command.NextChar, "space", 0, 1, true},
{command.PreviousChar, "É", 2, 0, true},
{command.NextChar, "space", 0, 1, true},
{command.NextChar, "space", 1, 1, false},
{command.NextChar, "space", 2, 1, false},
{command.NextChar, "界", 0, 2, true},
} {
result := r.Execute(trial.id)
if got := result.Text; got != trial.text || r.x != trial.x || r.y != trial.y || result.Cue != "" {
t.Fatalf("%s: %q at %d,%d", trial.id, got, r.x, r.y)
}
if result.Character == nil {
t.Fatal("lost character metadata")
}
if (result.After.Text == "line break") != (feedback && trial.crossing) || (result.After.Cue == presentation.LineEnd) != (feedback && trial.crossing) {
t.Fatal(result.After.Text, result.After.Cue)
}
}
r.Execute(command.ScreenLastChar)
result := r.Execute(command.NextChar)
text := result.Text
if feedback && (text != "end of screen" || result.Cue != presentation.Bottom) || !feedback && (text != "" || result.Cue != "") {
t.Fatal(text, result.Cue)
}
if r.x != 2 || r.y != 2 || result.After.Text != "" {
t.Fatal("screen edge moved cursor or retained line break")
}
r.Execute(command.Top)
text = r.Execute(command.PreviousChar).Text
if feedback && text != "start of screen" || !feedback && text != "" {
t.Fatal(text)
}
}
}
func TestWordSelectionFromInsideAndBetweenWords(t *testing.T) {
for _, trial := range []struct {
x, y int
id command.ID
text string
targetX, targetY int
crossing bool
}{
{4, 0, command.CurrentWord, "alpha", 2, 0, false},
{4, 0, command.PreviousWord, "start of screen", 4, 0, false},
{9, 0, command.PreviousWord, "alpha", 2, 0, false},
{8, 0, command.CurrentWord, "alpha", 2, 0, false},
{8, 0, command.NextWord, "beta!", 9, 0, false},
{0, 1, command.CurrentWord, "beta!", 9, 0, true},
{0, 1, command.PreviousWord, "alpha", 2, 0, true},
{9, 0, command.NextWord, "界é", 1, 2, true},
{1, 2, command.PreviousWord, "beta!", 9, 0, true},
{0, 0, command.CurrentWord, "start of screen", 0, 0, false},
{8, 2, command.NextWord, "end of screen", 8, 2, false},
} {
var r Review
s := fixture(" alpha beta!", "", " 界é last")
s.X, s.Y = trial.x, trial.y
r.Update(s)
result := r.Execute(trial.id)
got := result.Text
if got != trial.text || r.x != trial.targetX || r.y != trial.targetY || (result.After.Text == "line break") != trial.crossing {
t.Fatalf("%+v: %q at %d,%d after=%q", trial, got, r.x, r.y, result.After.Text)
}
if s.X != r.screen.X || s.Y != r.screen.Y {
t.Fatal("moved application cursor")
}
}
var r Review
r.Update(fixture("", ""))
for _, id := range []command.ID{command.CurrentWord, command.PreviousWord, command.NextWord} {
result := r.Execute(id)
if got := result.Text; got != "blank" || result.Cue != presentation.Blank {
t.Fatal(id, got, result.Cue)
}
}
}
func TestReviewLineEndpointsAndTopBottom(t *testing.T) {
var r Review
s := fixture(" Éx!", "\t\u2003", "last")
s.X = 3
r.Update(s)
for _, trial := range []struct {
id command.ID
text, after string
x, y int
}{
{command.FirstText, "É", "first character in line indent 2", 2, 0},
{command.LastText, "bang", "last character in line", 4, 0},
{command.NextLine, "\t\u2003", "", 4, 1},
{command.FirstText, "line is empty", "", 4, 1},
{command.LastText, "line is empty", "", 4, 1},
{command.LineStart, "blank", "beginning of line", 0, 1},
{command.LineEnd, "space", "end of line", 19, 1},
{command.CurrentLine, "\t\u2003", "", 0, 1},
{command.Bottom, "Bottom", "", 0, 2},
{command.NextChar, "a", "", 1, 2},
{command.Top, "Top", "", 0, 0},
} {
// Whitespace-only row content is presented as blank, including Unicode.
want := trial.text
if trial.id == command.NextLine || trial.id == command.CurrentLine {
want = "blank"
}
result := r.Execute(trial.id)
if got := result.Text; got != want || result.After.Text != trial.after || r.x != trial.x || r.y != trial.y {
t.Fatalf("%s: %q after=%q at %d,%d", trial.id, got, result.After.Text, r.x, r.y)
}
}
}
func TestTableBoundaryContextAndModeSynchronization(t *testing.T) {
var r Review
r.Update(fixture("Name|Subject|Empty", "José|É!|"))
r.Set_table_mode(true)
r.Execute(command.CurrentWord)
r.Set_mark()
r.Execute(command.NextLine)
for _, trial := range []struct {
id command.ID
text, after string
cue presentation.Cue
}{
{command.NextWord, "É! Subject", "", ""},
{command.PreviousChar, "É", "start of cell", presentation.LineStart},
{command.NextChar, "bang", "", ""},
{command.NextChar, "bang", "end of cell", presentation.LineEnd},
{command.NextWord, "blank Empty", "", ""},
{command.NextChar, "blank", "end of cell", presentation.LineEnd},
{command.NextWord, "blank Empty", "end of line", presentation.LineEnd},
{command.LineStart, "José Name", "first cell", ""},
{command.PreviousWord, "José Name", "start of line", presentation.LineStart},
{command.LineEnd, "blank Empty", "last cell", ""},
} {
result := r.Execute(trial.id)
if got := result.Text; got != trial.text || result.After.Text != trial.after || result.After.Cue != trial.cue {
t.Fatalf("%s: %q after=%q cue=%q", trial.id, got, result.After.Text, result.After.Cue)
}
}
r.Execute(command.Top)
if got := r.Execute(command.CurrentWord).Text; got != "Name Name" {
t.Fatal("Top kept old table column", got)
}
r.Execute(command.LineEnd)
r.Execute(command.Bottom)
if got := r.Execute(command.CurrentWord).Text; got != "José Name" {
t.Fatal("Bottom kept old table column", got)
}
r.Execute(command.FollowCursor)
r.Set_table_mode(false)
r.Execute(command.LineEnd)
r.Set_table_mode(true)
if got := r.Execute(command.CurrentWord).Text; got != "Empty Column 3" {
t.Fatal("mode change did not resynchronize", got)
}
}
func TestReviewScreenChangePolicyAndClamping(t *testing.T) {
for _, leave := range []bool{false, true} {
var r Review
r.Configure(true, true, leave)
s := fixture("header|value", "data|cell")
r.Update(s)
r.Set_table_mode(true)
r.Execute(command.CurrentWord)
r.Set_mark()
r.Execute(command.Bottom)
r.Execute(command.LineEnd)
s.Terminal++
s.Generation++
r.Update(s)
if r.Active() == leave || len(r.tableHeaders) != 0 {
t.Fatal("wrong review/header ownership", r.Active())
}
if leave && (r.x != 0 || r.y != 0) || !leave && (r.x != 5 || r.y != 1) {
t.Fatal(r.x, r.y)
}
// A resized screen keeps the anchor only when explicitly requested,
// and bounds it before any action can index cells.
r.Execute(command.ScreenLastChar)
r.Update(fixture("x"))
r.Execute(command.CurrentChar)
r.Update(screen.Snapshot{})
if r.Active() {
t.Fatal("inactive session retained review")
}
}
}