// Copyright (c) 2026 Storm Dragon // SPDX-License-Identifier: GPL-3.0-or-later package review import ( "testing" "git.stormux.org/storm/fenrir/internal/command" "git.stormux.org/storm/fenrir/internal/presentation" ) func TestSpellingUsesSourceAtDetachedReviewPosition(t *testing.T) { var review Review snapshot := fixture("A book!?É界", "other") snapshot.X = 3 review.Update(snapshot) for _, trial := range []struct { id command.ID text string phonetic bool }{ {command.SpellWord, "book!?É界", false}, {command.PhoneticWord, "book!?É界", true}, {command.SpellLine, "A book!?É界", false}, {command.PhoneticLine, "A book!?É界", true}, {command.PhoneticChar, "o", true}, } { result := review.Execute(trial.id) if got := result.Text; got != trial.text { t.Fatal(trial.id, got) } spelling := result.Spelling if spelling == nil || spelling.Text != trial.text || spelling.Phonetic != trial.phonetic { t.Fatal(trial.id, spelling) } if result.Character != nil || result.Cue != "" { t.Fatal("source was converted to character feedback or a cue") } // Application motion and passive updates must not move detached review. snapshot.X, snapshot.Y = 0, 1 review.Update(snapshot) if review.x != 3 || review.y != 0 || !review.Active() { t.Fatal("spelling moved or lost review") } } result := review.Execute(command.CurrentWord) if result.Spelling != nil { t.Fatal("spelling leaked into ordinary review") } snapshot.Active = false review.Update(snapshot) result = review.Execute(command.SpellWord) if result.Spelling != nil || result.Cue != presentation.Error { t.Fatal("unavailable screen was spelled") } } func TestSpellingBlanksAndLiteralAnnouncements(t *testing.T) { for _, text := range []string{"", " ", "blank", "space"} { for _, id := range []command.ID{command.SpellWord, command.PhoneticWord, command.SpellLine, command.PhoneticLine, command.PhoneticChar} { var review Review review.Update(fixture(text)) result := review.Execute(id) if text == "" || text == " " { if result.Spelling != nil || result.Cue != presentation.Blank { t.Fatal(text, id, result.Spelling, result.Cue) } } else if result.Spelling == nil || result.Cue != "" { t.Fatal("literal announcement was not spelled", text, id) } } } } func TestTableSpellingKeepsCellScopeAndExcludesHeaders(t *testing.T) { var review Review snapshot := fixture("Name|Subject|Empty", "Same|A book!||Tail") review.Update(snapshot) review.Set_table_mode(true) review.Execute(command.CurrentWord) review.Set_mark() review.Execute(command.NextLine) review.Execute(command.NextWord) if got := review.Execute(command.CurrentWord).Text; got != "A book! Subject" { t.Fatal(got) } for _, id := range []command.ID{command.SpellWord, command.PhoneticWord} { result := review.Execute(id) if got := result.Text; got != "A book!" || result.Spelling == nil { t.Fatal(id, got, result.Spelling) } } if got := review.Execute(command.PhoneticChar).Text; got != "A" { t.Fatal(got) } if got := review.Execute(command.PhoneticLine).Text; got != "Same|A book!||Tail" { t.Fatal("line scope was restricted to a cell", got) } review.Execute(command.NextWord) result := review.Execute(command.PhoneticWord) if result.Spelling != nil || result.Cue != presentation.Blank { t.Fatal("empty cell was spelled as a header", result.Spelling) } result = review.Execute(command.PhoneticChar) if result.Spelling != nil || result.Cue != presentation.Blank { t.Fatal("empty cell character escaped its cell") } review.Update(fixture("")) result = review.Execute(command.PhoneticWord) if got := result.Text; got != "no table data" || result.Spelling != nil { t.Fatal("table error was spelled", got) } }