cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

cat/module-path
012 / internal / ui / nbview / jupyter_test.go
jupyter_test.go 5.4K
1package nbview23import (4 "fmt"5 "slices"6 "strings"7 "testing"89 "github.com/charmbracelet/x/ansi"1011 "src.rest/cat/012/internal/notebook"12)1314// at is the body line holding text and the column it starts at.15func at(t *testing.T, v *View, want string) (x, y int) {16 t.Helper()17 for y, l := range v.Lines() {18 if i := strings.Index(ansi.Strip(l), want); i >= 0 {19 return ansi.StringWidth(ansi.Strip(l)[:i]), y20 }21 }22 t.Fatalf("no line holds %q:\n%s", want, text(v))23 return 0, 024}2526func TestToolbarFitsAndClicks(t *testing.T) {27 h := newHost("ls")28 v := newView(h, 120, 20)29 wide := ansi.Strip(v.Toolbar(120))30 for _, want := range []string{"▶ Run", "Shift+Enter", "■ Stop", "▶▶ Run all", "+ Add", "✂ Cut", "Code ▾", "nu ○ idle"} {31 if !strings.Contains(wide, want) {32 t.Errorf("at 120 the toolbar lacks %q: %q", want, wide)33 }34 }35 narrow := ansi.Strip(v.Toolbar(60))36 if ansi.StringWidth(narrow) > 60 || !strings.Contains(narrow, "▶ Run") || !strings.Contains(narrow, "nu ○ idle") || strings.Contains(narrow, "Shift+Enter") {37 t.Errorf("at 60: %q", narrow)38 }39 x := strings.Index(wide, "▶ Run")40 if got := v.ToolbarAt(x+2, 120); got != "nb.run_next" {41 t.Errorf("Run: %q", got)42 }43 stop := ansi.StringWidth(wide[:strings.Index(wide, "■ Stop")])44 if got := v.ToolbarAt(stop, 120); got != "" {45 t.Errorf("Stop with nothing running: %q", got)46 }47 h.kernel = Kernel{Busy: true, Waiting: 2}48 if got := v.ToolbarAt(stop, 120); got != "nb.stop" || !strings.Contains(ansi.Strip(v.Toolbar(120)), "nu ● busy, 2 waiting") {49 t.Errorf("Stop while busy: %q", got)50 }51 h.cells[0].Kind = notebook.Note52 if !strings.Contains(ansi.Strip(v.Toolbar(120)), "Markdown ▾") {53 t.Error("the kind isn't the cell's")54 }55}5657func TestClicksSelectEditRunAndFold(t *testing.T) {58 h := newHost("files = ls", "$files | first", "# Title")59 h.cells[2].Kind = notebook.Note60 h.outs[1] = &notebook.Output{NUON: []byte("[[name]; [a], [b]]"), Count: 1}61 v := newView(h, 80, 40)62 x, y := at(t, v, "first")63 v.Click(x+2, y, false) // into the second cell's code64 if i, _ := v.Selected(); i != 1 || !v.Editing() {65 t.Fatalf("a click in the box: cell %d, editing %v", i, v.Editing())66 }67 if got := v.edit.area.Pos; got != len([]rune("$files | fi")) {68 t.Errorf("the caret went to %d", got)69 }70 _, y = at(t, v, "files = ls")71 v.Click(v.width-1, y, false) // ▶72 if v.Editing() || !slices.Contains(h.ran, "nb.run") {73 t.Errorf("▶: editing %v, ran %v", v.Editing(), h.ran)74 }75 _, y = at(t, v, "Out[1]:")76 v.Click(2, y, false) // left of the output77 if !v.Hidden(1) || !strings.Contains(text(v), "output hidden") {78 t.Errorf("folding:\n%s", text(v))79 }80 v.Click(2, y, false)81 if v.Hidden(1) {82 t.Error("unfolding")83 }84 _, y = at(t, v, "Title")85 v.Click(20, y, true)86 if from, to := v.Range(); from != 0 || to != 2 {87 t.Errorf("Shift+click: %d to %d", from, to)88 }89 v.lastTap = tap{} // a while later90 v.Click(20, y, false)91 v.Click(20, y, false)92 if i, _ := v.Selected(); i != 2 || !v.Editing() || !strings.Contains(text(v), "# Title") {93 t.Errorf("a double click on a note edits its Markdown:\n%s", text(v))94 }95}9697func TestOutputScrollsInItsWindow(t *testing.T) {98 h := newHost("ls", "after")99 var b strings.Builder100 b.WriteString("[[n]; ")101 for i := range 45 {102 fmt.Fprintf(&b, "[%d], ", i)103 }104 b.WriteString("[x]]")105 h.outs[1] = &notebook.Output{NUON: []byte(b.String()), Count: 1}106 v := newView(h, 80, 40)107 _, afterY := at(t, v, "after")108 if !strings.Contains(text(v), "36 more rows") {109 t.Fatalf("the window:\n%s", text(v))110 }111 v.Key(key("down")) // onto the output112 v.Key(key("down")) // scrolls it113 v.Key(key("down"))114 if got := text(v); !strings.Contains(got, "rows 3 to 12 of 46") || !strings.Contains(got, " 11\n") && !strings.Contains(got, " 11 ") {115 t.Errorf("scrolled by keys:\n%s", got)116 }117 if _, y := at(t, v, "after"); y != afterY {118 t.Errorf("the cell under moved from %d to %d", afterY, y)119 }120 _, y := at(t, v, "rows 3 to 12")121 v.Wheel(y-2, 3)122 if !strings.Contains(text(v), "rows 6 to 15 of 46") {123 t.Errorf("scrolled by the wheel:\n%s", text(v))124 }125 v.Key(key("O"))126 v.ToggleWhole()127 if strings.Contains(text(v), "more rows") || strings.Contains(text(v), "rows 6") {128 t.Errorf("whole:\n%s", text(v))129 }130 v.ToggleHidden()131 if !strings.Contains(text(v), "output hidden") {132 t.Errorf("hidden:\n%s", text(v))133 }134}135136func TestSelectsSeveralCells(t *testing.T) {137 h := newHost("a", "b", "c", "d")138 v := newView(h, 80, 40)139 v.Key(key("down"))140 v.Key(key("J"))141 v.Key(key("J"))142 if from, to := v.Range(); from != 1 || to != 3 {143 t.Errorf("J J: %d to %d", from, to)144 }145 v.Key(key("K"))146 if from, to := v.Range(); from != 1 || to != 2 {147 t.Errorf("K: %d to %d", from, to)148 }149 v.Key(key("up"))150 if from, to := v.Range(); from != 1 || to != 1 {151 t.Errorf("up selects one: %d to %d", from, to)152 }153}154155func TestOutline(t *testing.T) {156 h := newHost("# Intro\ntext\n## Part", "files = ls", "no heading")157 h.cells[0].Kind = notebook.Note158 h.cells[2].Kind = notebook.Note159 v := newView(h, 80, 40)160 var got []string161 for _, e := range v.Outline(false) {162 got = append(got, fmt.Sprintf("%d %d %s", e.Cell, e.Level, e.Title))163 }164 if !slices.Equal(got, []string{"0 1 Intro", "0 2 Part"}) {165 t.Errorf("contents %q", got)166 }167 got = nil168 for _, e := range v.Outline(true) {169 got = append(got, fmt.Sprintf("%d %d %s", e.Cell, e.Level, e.Title))170 }171 if !slices.Equal(got, []string{"0 1 Intro", "0 2 Part", "1 0 files = ls", "2 0 no heading"}) {172 t.Errorf("cells %q", got)173 }174}