package ui
import (
"strings"
"testing"
"updater-tui/internal/plan"
)
// adversarial strings that a PKGBUILD could genuinely contain in a description,
// a source URL or an audit evidence line
var ugly = []string{
"日本語のパッケージ説明",
"🎉🚀 emoji in a description",
"combining: e\u0301\u0301\u0301\u0301",
"zero\u200bwidth\u200bspaces",
"RTL \u202eoverride\u202c text",
"a\tb\tc",
strings.Repeat("x", 300),
"",
"\x1b[31mraw escape\x1b[0m",
"ff ligature and fi",
"👨👩👧👦 family zwj",
}
func TestRowsNeverOverflowWithUnicodeContent(t *testing.T) {
for _, s := range ugly {
doc := &plan.Plan{
Aur: []plan.AurUpdate{{Name: "pkg", Old: "1-1", New: "2-1", Tag: "!!",
Note: s}},
Repo: []plan.Upgrade{{Name: "r", Old: s, New: s, Delta: -1}},
Vcs: []plan.Vcs{{Name: "v-git", Old: s, New: s}},
Checks: []plan.Check{{OK: false, Label: s, Detail: s}},
Audits: map[string][]plan.Finding{
"pkg": {{Rule: s, Severity: "BLOCK", Title: s, Evidence: s}},
},
Advisories: []plan.Advisory{{ID: s, Severity: s, Fixed: s,
Packages: []string{s}, Issues: []string{s}}},
}
for _, width := range []int{60, 80, 100, 160} {
m := loaded(t, doc, width, 40)
m.modal = nil
for i, line := range strings.Split(m.View(), "\n") {
if got := displayWidth(line); got > width {
t.Errorf("width=%d content=%q: line %d is %d cells: %q",
width, s, i, got, plain(line))
}
}
// and every overlay must survive the same content
for _, show := range []string{"help", "confirm"} {
m2 := loaded(t, doc, width, 40)
switch show {
case "help":
m2.modal = newHelp()
case "confirm":
updated, _ := m2.confirmApply()
m2 = updated.(Model)
}
if m2.modal == nil {
continue
}
for i, line := range strings.Split(m2.modal.View(width, 40), "\n") {
if got := displayWidth(line); got > width {
t.Errorf("%s width=%d content=%q: line %d is %d cells: %q",
show, width, s, i, got, plain(line))
}
}
}
}
}
}
func TestDetailOverlayNeverOverflows(t *testing.T) {
for _, s := range ugly {
d := &plan.Detail{
Package: s, Installed: s, Available: s, BaselineNote: s,
Diff: plan.Diff{Added: 1, Removed: 1, Lines: []plan.DiffLine{
{Kind: "add", Text: s}, {Kind: "del", Text: s}, {Kind: "gap"},
{Kind: "ctx", Text: s}}},
Findings: []plan.Finding{{Rule: s, Severity: "BLOCK", Title: s, Evidence: s}},
Advisories: []plan.Advisory{{ID: s, Severity: s, Fixed: s, Issues: []string{s}}},
Pkgbuild: []string{s, s},
Meta: plan.Meta{Maintainer: s, URL: s, Depends: []string{s}},
}
for _, width := range []int{60, 100, 160} {
for tab := 0; tab < 4; tab++ {
modal := newDetail("pkg", width)
modal.Update(detailMsg{pkg: "pkg", detail: d})
modal.tab = tab
modal.built = false
for i, line := range strings.Split(modal.View(width, 40), "\n") {
if got := displayWidth(line); got > width {
t.Errorf("tab=%d width=%d content=%q: line %d is %d cells",
tab, width, s, i, got)
}
}
}
}
}
}
func TestTextAndAdvisoryModalsNeverOverflow(t *testing.T) {
for _, s := range ugly {
for _, lines := range [][]string{
advisoryLines(&plan.AdvisoryDetail{ID: s, Available: true, Severity: s,
Kind: s, Affected: s, Fixed: s, Packages: []string{s},
Issues: []string{s, s}, References: []string{s}, Notes: s, URL: s}),
securityLines(&plan.SecurityReport{Available: true, Total: 1, Fixable: 1,
Advisories: []plan.Advisory{{ID: s, Severity: s, Fixed: s,
Packages: []string{s}, Issues: []string{s}}}}),
} {
for _, width := range []int{60, 100, 160} {
m := newText("t")
m.Update(textBodyMsg{title: "t", lines: lines})
for i, line := range strings.Split(m.View(width, 40), "\n") {
if got := displayWidth(line); got > width {
t.Errorf("width=%d content=%q: line %d is %d cells",
width, s, i, got)
}
}
}
}
}
}
func TestLoadingViewNeverOverflows(t *testing.T) {
for _, width := range []int{50, 60, 100} {
m := New(plan.Backend{}, testConfig(t))
m.width, m.height = width, 20
m.status = "日本語のとても長いステータスメッセージ"
for i, line := range strings.Split(m.View(), "\n") {
if got := displayWidth(line); got > width {
t.Errorf("width=%d: line %d is %d cells: %q", width, i, got, plain(line))
}
}
}
}