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)) } } } }