1package suggest23import (4 "strings"5 "testing"67 tea "charm.land/bubbletea/v2"8 "github.com/charmbracelet/x/ansi"910 "src.rest/cat/012/internal/sheet"11 "src.rest/cat/012/internal/ui/lineedit"12 "src.rest/cat/012/internal/ui/overlay"13 "src.rest/cat/012/internal/ui/theme"14)1516// fakeHost is an entry being typed on Sheet1 of a book that also has17// Summary and a range named Sales.18type fakeHost struct {19 th theme.Theme20 line lineedit.Line21 s *sheet.Sheet22 typing bool23}2425func newHost(t *testing.T) *fakeHost {26 t.Helper()27 s := sheet.New()28 if _, err := s.Book().AddSheet("Summary", 1); err != nil {29 t.Fatal(err)30 }31 if err := s.DefineName("Sales", sheet.Rect{To: sheet.Addr{Row: 4}}); err != nil {32 t.Fatal(err)33 }34 return &fakeHost{th: theme.New(true), s: s, typing: true}35}3637func (h *fakeHost) Theme() *theme.Theme { return &h.th }38func (h *fakeHost) Size() (int, int) { return 80, 24 }39func (h *fakeHost) Line() *lineedit.Line { return &h.line }40func (h *fakeHost) Typing() bool { return h.typing }41func (h *fakeHost) EntrySheet() *sheet.Sheet { return h.s }42func (h *fakeHost) Stored(buf []rune) []rune { return buf }43func (h *fakeHost) TextX() int { return 12 }4445func names(list []Suggestion) []string {46 var out []string47 for _, s := range list {48 out = append(out, s.Name)49 }50 return out51}5253func TestOffersNamesSheetsThenFunctions(t *testing.T) {54 h := newHost(t)55 got := names(For(h.s, "s"))56 if len(got) < 3 || got[0] != "Sales" || got[1] != "Summary!" || got[2] != "SEARCH" && !strings.HasPrefix(got[2], "S") {57 t.Fatalf("for s: %v", got[:min(len(got), 5)])58 }59 if got := names(For(h.s, "'su")); len(got) != 1 || got[0] != "Summary!" {60 t.Fatalf("a quoted word offers only sheets: %v", got)61 }62 if got := names(For(h.s, "ifs")); !strings.Contains(strings.Join(got, " "), "COUNTIFS") {63 t.Fatalf("for ifs: %v", got)64 }65}6667// Tables are offered with named ranges; in a table's brackets its68// columns and items are, and the context line lists its columns.69func TestOffersTablesAndColumns(t *testing.T) {70 h := newHost(t)71 s := h.s72 for a, v := range map[string]string{"C1": "Region", "D1": "Unit Price", "E1": "Q[1]"} {73 s.Set(sheet.Addr{Col: int(a[0] - 'A')}, v)74 }75 if err := s.CreateTable("Orders", sheet.Rect{From: sheet.Addr{Col: 2}, To: sheet.Addr{Col: 4, Row: 3}}); err != nil {76 t.Fatal(err)77 }78 if got := names(For(s, "ord")); len(got) == 0 || got[0] != "Orders" {79 t.Fatalf("for ord: %v", got)80 }81 var l List82 h.line.Set("=Orders[u")83 l.Active = true84 list, start := l.Shown(h)85 if strings.Join(names(list), " ") != "Unit Price" || start != len("=Orders[") {86 t.Fatalf("columns %v from %d", names(list), start)87 }88 l.Key(h, "tab")89 if h.line.Text() != "=Orders[Unit Price]" {90 t.Fatalf("accepted %q", h.line.Text())91 }92 h.line.Set("=Orders[@q")93 l.Active = true94 l.Key(h, "tab")95 if h.line.Text() != "=Orders[@Q'[1']]" {96 t.Fatalf("an escaped column after @: %q", h.line.Text())97 }98 h.line.Set("=Orders[#")99 l.Active = true100 if list, _ := l.Shown(h); strings.Join(names(list), " ") != "#All #Data #Headers #This Row" {101 t.Fatalf("items %v", names(list))102 }103 left, _, ok := TableLine(&h.th, 80, s, []rune("=Orders[Uni"), len("=Orders[Uni"), "")104 if !ok || !strings.Contains(ansi.Strip(left), "Orders[Region, Unit Price, Q[1]]") || !strings.Contains(left, h.th.Argument.Render("Unit Price")) {105 t.Errorf("context %q", left)106 }107}108109func TestKeysMoveAndAccept(t *testing.T) {110 h := newHost(t)111 var l List112 h.line.Set("=su")113 if list, _ := l.Shown(h); list != nil {114 t.Fatal("the list shows before a key typed text")115 }116 l.Active = true117 list, start := l.Shown(h)118 if len(list) == 0 || start != 1 {119 t.Fatalf("list %v from %d", names(list), start)120 }121 for list[l.Sel()].Name != "SUM" {122 l.Key(h, "down")123 }124 if !l.Key(h, "tab") || h.line.Text() != "=SUM(" || h.line.Pos != 5 || l.Active {125 t.Fatalf("accepted %q at %d, active %v", h.line.Text(), h.line.Pos, l.Active)126 }127 if l.Key(h, "down") {128 t.Fatal("a hidden list took a key")129 }130 h.line.Set("=Summ!A1")131 h.line.Pos = len("=Summ")132 l.Active = true133 l.Key(h, "enter")134 if h.line.Text() != "=Summary!A1" {135 t.Fatalf("a sheet replaced its name as %q", h.line.Text())136 }137 h.typing, l.Active = false, true138 h.line.Set("=su")139 if list, _ := l.Shown(h); list != nil {140 t.Fatal("the list shows while something is open over the entry")141 }142}143144func TestBoxAndMouse(t *testing.T) {145 h := newHost(t)146 l := List{Active: true}147 h.line.Set("=co")148 b, ok := l.Box(h)149 if !ok || b.Y != overlay.ContextLine+1 || b.X != 12+1-2 || b.Height() != Rows+2 {150 t.Fatalf("box at %d,%d, %d high", b.X, b.Y, b.Height())151 }152 if !strings.Contains(ansi.Strip(strings.Join(b.Lines, "\n")), "of ") {153 t.Error("a long list says where it is")154 }155 if l.Mouse(h, tea.MouseClickMsg{X: 70, Y: 20, Button: tea.MouseLeft}) {156 t.Error("a click outside the list was taken")157 }158 l.Mouse(h, tea.MouseMotionMsg{X: b.X + 2, Y: b.Y + 3})159 if l.Sel() != 2 {160 t.Errorf("hovering the third row highlighted %d", l.Sel())161 }162 list, _ := l.Shown(h)163 want := list[2].Name164 l.Mouse(h, tea.MouseClickMsg{X: b.X + 2, Y: b.Y + 3, Button: tea.MouseLeft})165 if !strings.HasPrefix(h.line.Text(), "="+want) {166 t.Errorf("clicking inserted %q, want %s", h.line.Text(), want)167 }168 if desc, _, ok := (&List{Active: true}).Status(h); ok || desc != "" {169 t.Error("status with the list hidden")170 }171}172173func TestSignatureMarksTheArgument(t *testing.T) {174 th := theme.New(true)175 buf := []rune("=SUM(1,2")176 sig, desc := Signature(&th, buf, len(buf), ';')177 if !strings.Contains(sig, th.Argument.Render("[value2; ...]")) || desc == "" {178 t.Fatalf("signature %q, %q", sig, desc)179 }180 if left, _, ok := SignatureLine(&th, 20, []rune("=1+2"), 4, ',', ""); ok || left != "" {181 t.Fatalf("outside a function: %q", left)182 }183}