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 / nbcells_test.go
nbcells_test.go 5.0K
1package ui23import (4 "slices"5 "strings"6 "testing"78 tea "charm.land/bubbletea/v2"9 "github.com/charmbracelet/x/ansi"1011 "src.rest/cat/012/internal/notebook"12)1314// notebookOf is a notebook model with code cells of srcs, the first15// selected, in command mode.16func notebookOf(t *testing.T, out map[string]string, srcs ...string) (*Model, *fakeNu) {17 t.Helper()18 m, nu := notebookModel(t, out)19 var cells []notebook.Cell20 for _, s := range srcs {21 cells = append(cells, notebook.Cell{Source: s})22 }23 m.setCells("cells", cells)24 m.nbView().Select(0, false)25 return m, nu26}2728// screenX is the column text starts at on screen line y.29func screenX(t *testing.T, m *Model, y int, text string) int {30 t.Helper()31 l := line(m, y)32 i := strings.Index(l, text)33 if i < 0 {34 t.Fatalf("line %d lacks %q: %q", y, text, l)35 }36 return ansi.StringWidth(l[:i])37}3839func TestNotebookMovesCells(t *testing.T) {40 m, _ := notebookOf(t, nil, "a", "b", "c")41 press(t, m, "<down>", "<alt+down>")42 if got := cells(m); !slices.Equal(got, []string{"a", "c", "b"}) {43 t.Fatalf("Alt+Down: %q", got)44 }45 if i, _ := m.nbView().Selected(); i != 2 {46 t.Errorf("the cell moved isn't selected: %d", i)47 }48 press(t, m, "<shift+up>", "<alt+up>")49 if got := cells(m); !slices.Equal(got, []string{"c", "b", "a"}) {50 t.Fatalf("two cells up: %q", got)51 }52 if from, to := m.nbView().Range(); from != 0 || to != 1 {53 t.Errorf("the cells moved aren't selected: %d to %d", from, to)54 }55 press(t, m, "z")56 if got := cells(m); !slices.Equal(got, []string{"a", "c", "b"}) {57 t.Errorf("undo: %q", got)58 }59}6061func TestNotebookActsOnSeveralCells(t *testing.T) {62 m, nu := notebookOf(t, map[string]string{"a": "1", "b": "2"}, "a", "b", "c")63 press(t, m, "<shift+down>", "c", "<end>", "V")64 if got := cells(m); !slices.Equal(got, []string{"a", "b", "a", "b", "c"}) {65 t.Fatalf("copy, paste above: %q", got)66 }67 if from, to := m.nbView().Range(); from != 2 || to != 3 {68 t.Errorf("what was pasted isn't selected: %d to %d", from, to)69 }70 press(t, m, "d", "d")71 if got := cells(m); !slices.Equal(got, []string{"a", "b", "c"}) || !strings.Contains(m.note, "cells 3 to 4") {72 t.Fatalf("d d: %q, %q", got, m.note)73 }74 press(t, m, "z")75 if got := cells(m); len(got) != 5 {76 t.Errorf("z: %q", got)77 }78 press(t, m, "<home>", "<shift+down>", "<ctrl+enter>")79 if got := nu.ran(); !slices.Equal(got, []string{"a", "b"}) {80 t.Errorf("running two cells ran %q", got)81 }82 press(t, m, "m")83 if c := m.sheet.NotebookCells(); c[0].Kind != notebook.Note || c[1].Kind != notebook.Note || c[2].Kind != notebook.Code {84 t.Error("m made the two cells notes")85 }86}8788// The toolbar's buttons run their commands; Code ▾ opens the kinds.89func TestNotebookToolbar(t *testing.T) {90 m, nu := notebookOf(t, map[string]string{"ls": lsOut}, "ls")91 if got := line(m, formulaLine); !strings.Contains(got, "▶ Run") || !strings.Contains(got, "nu ○ idle") {92 t.Fatalf("the toolbar: %q", got)93 }94 send(m, tea.MouseClickMsg{X: screenX(t, m, formulaLine, "▶ Run") + 1, Y: formulaLine, Button: tea.MouseLeft})95 if len(nu.jobs) != 1 || !strings.Contains(screen(m), "Out[1]:") {96 t.Fatalf("▶ Run ran %v:\n%s", nu.ran(), screen(m))97 }98 send(m, tea.MouseClickMsg{X: screenX(t, m, formulaLine, "Code ▾"), Y: formulaLine, Button: tea.MouseLeft})99 if m.overlay == nil {100 t.Fatal("Code ▾ didn't open the kinds")101 }102 press(t, m, "<down>", "<enter>")103 if c := m.sheet.NotebookCells(); c[len(c)-1].Kind != notebook.Note {104 t.Errorf("Markdown didn't make the cell a note: %+v", c)105 }106}107108// A click on a cell's ▶ runs it, and a click in its box edits it.109func TestNotebookMouse(t *testing.T) {110 m, nu := notebookOf(t, map[string]string{"ls": lsOut}, "ls", "b")111 y := nbBody + 1 // the first cell's code112 send(m, tea.MouseClickMsg{X: m.width - 1, Y: y, Button: tea.MouseLeft})113 if len(nu.jobs) != 1 {114 t.Fatalf("▶ ran %v", nu.ran())115 }116 send(m, tea.MouseClickMsg{X: 14, Y: y, Button: tea.MouseLeft})117 if !m.nbView().Editing() || !strings.Contains(line(m, menuLine), "EDIT") {118 t.Error("a click in the box didn't edit the cell")119 }120 send(m, tea.MouseClickMsg{X: 14, Y: y, Button: tea.MouseRight})121 if m.overlay == nil {122 t.Error("a right click didn't open the cell menu")123 }124}125126func TestNotebookGoToCell(t *testing.T) {127 m, _ := notebookOf(t, nil, "a", "sales = open x.csv", "c")128 press(t, m, "<ctrl+g>")129 if m.overlay == nil {130 t.Fatal("Ctrl+G opened nothing")131 }132 m.line.Set("sales")133 m.overlay.(interface{ Changed() }).Changed()134 press(t, m, "<enter>")135 if i, _ := m.nbView().Selected(); i != 1 || m.overlay != nil {136 t.Errorf("went to cell %d", i+1)137 }138 run(m, m.runCommand("nb.toc"))139 if !strings.Contains(m.note, "no headings") {140 t.Errorf("a table of contents without headings: %q", m.note)141 }142}143144func TestNotebookClearsOneOutput(t *testing.T) {145 m, _ := notebookOf(t, map[string]string{"a": "1", "b": "2"}, "a", "b")146 run(m, m.runCommand("nb.run_all"))147 run(m, m.runCommand("nb.clear_output"))148 c := m.sheet.NotebookCells()149 if m.book().Output(c[0].ID) != nil || m.book().Output(c[1].ID) == nil {150 t.Error("Clear output cleared the wrong outputs")151 }152}