1package ui23import (4 "strings"5 "testing"67 tea "charm.land/bubbletea/v2"89 "src.rest/cat/012/internal/sheet"10)1112func leftAt(x, y int) tea.Mouse { return tea.Mouse{X: x, Y: y, Button: tea.MouseLeft} }1314// barText is the text in the formula bar after the name box.15func barText(m *Model) string {16 l := line(m, formulaLine)17 if len(l) <= formulaBarTextX() {18 return ""19 }20 return strings.TrimSpace(l[formulaBarTextX():])21}2223func TestClickWhileTypingAccepts(t *testing.T) {24 m := newModel()25 press(t, m, "hello")26 click(m, cellX(2), gridTop+3, 0)27 if input(m, "A1") != "hello" || m.cur != addr("C4") || m.mode != modeReady {28 t.Errorf("A1 %q cur %v mode %v", input(m, "A1"), m.cur, m.mode)29 }30}3132func TestClickInsertsReferenceIntoFormula(t *testing.T) {33 m := newModel()34 press(t, m, "5", "<enter>", "7", "<enter>", "=")35 click(m, cellX(0), gridTop, 0)36 if m.mode != modePoint || barText(m) != "=A1" {37 t.Fatalf("mode %v formula bar %q", m.mode, barText(m))38 }39 // Dragging makes it a range.40 send(m, tea.MouseClickMsg(leftAt(cellX(0), gridTop)))41 send(m, tea.MouseMotionMsg(leftAt(cellX(0), gridTop+1)))42 send(m, tea.MouseReleaseMsg(leftAt(cellX(0), gridTop+1)))43 if barText(m) != "=A1:A2" {44 t.Fatalf("formula bar %q", barText(m))45 }46 press(t, m, "*2", "<enter>")47 if got := m.sheet.Cell(addr("A3")); got == nil || got.Input != "=A1:A2*2" {48 t.Errorf("A3 = %+v", got)49 }50}5152func TestClickFormulaBarEdits(t *testing.T) {53 m := newModel()54 press(t, m, "=1+2", "<enter>", "<up>")55 line0, x0 := formulaBarAt()56 click(m, x0+2, line0, 0)57 if m.mode != modeEdit || m.line.Pos != 2 {58 t.Fatalf("mode %v caret %d", m.mode, m.line.Pos)59 }60 // Clicking in the edit line moves the caret.61 line1, x1 := editLineAt()62 click(m, x1+4, line1, 0)63 if m.line.Pos != 4 {64 t.Errorf("caret %d", m.line.Pos)65 }66}6768func TestResizeColumnByDragging(t *testing.T) {69 m := newModel()70 border := minRowHdrW + sheet.DefaultWidth - 171 send(m, tea.MouseMotionMsg{X: border, Y: headerLine})72 if m.mouse.hover.kind != hitColBorder || !strings.Contains(line(m, headerLine), "▐") {73 t.Fatalf("hover %v header %q", m.mouse.hover.kind, line(m, headerLine))74 }75 send(m, tea.MouseClickMsg(leftAt(border, headerLine)))76 send(m, tea.MouseMotionMsg(leftAt(border+5, headerLine)))77 if w := m.sheet.ColWidth(0); w != sheet.DefaultWidth+5 {78 t.Errorf("width while dragging %d", w)79 }80 if !strings.Contains(line(m, 2), "Column A width 15") {81 t.Errorf("context line %q", line(m, 2))82 }83 send(m, tea.MouseReleaseMsg(leftAt(border+5, headerLine)))84 if m.mouse.drag != dragNone || !m.changed {85 t.Error("resize did not finish")86 }87}8889func TestDoubleClickBorderAutofits(t *testing.T) {90 m := newModel()91 press(t, m, "A much longer label", "<enter>")92 border := minRowHdrW + sheet.DefaultWidth - 193 click(m, border, headerLine, 0)94 click(m, border, headerLine, 0)95 if w := m.sheet.ColWidth(0); w != len("A much longer label")+2 {96 t.Errorf("autofit width %d", w)97 }98}99100func TestDragAutoscrolls(t *testing.T) {101 m := newModel()102 send(m, tea.MouseClickMsg(leftAt(cellX(0), gridTop)))103 _, cmd := m.Update(tea.MouseMotionMsg(leftAt(cellX(0), m.height-1)))104 if cmd == nil || !m.mouse.autoscrolling {105 t.Fatal("dragging below the grid did not start autoscroll")106 }107 bottom := m.ext.Row108 for range 5 {109 send(m, autoscrollMsg{})110 }111 if m.ext.Row != bottom+5 || m.top == 0 {112 t.Errorf("ext %v top %d", m.ext, m.top)113 }114 send(m, tea.MouseReleaseMsg(leftAt(cellX(0), m.height-1)))115 send(m, autoscrollMsg{})116 if m.mouse.autoscrolling {117 t.Error("autoscroll kept going after release")118 }119}120121func TestPointerShapes(t *testing.T) {122 m := newModel()123 for _, tt := range []struct {124 x, y int125 want string126 }{127 {cellX(1), gridTop + 1, "cell"},128 {minRowHdrW + sheet.DefaultWidth - 1, headerLine, "col-resize"},129 {cellX(1), headerLine, "pointer"},130 {formulaBarTextX() + 1, formulaLine, "text"},131 {m.width - 1, m.height - 1, "default"},132 {1, m.height - 1, "pointer"}, // the sheet tab133 } {134 send(m, tea.MouseMotionMsg{X: tt.x, Y: tt.y})135 if m.mouse.shape != tt.want {136 t.Errorf("at %d,%d shape %q, want %q", tt.x, tt.y, m.mouse.shape, tt.want)137 }138 }139}140141func TestClickDismissesHelp(t *testing.T) {142 m := newModel()143 press(t, m, "<f1>")144 click(m, 1, m.height-1, 0) // outside the shortcuts overlay145 if m.mode != modeReady || m.overlay != nil {146 t.Errorf("mode %v overlay %T", m.mode, m.overlay)147 }148}