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 / e2e / jupyter_test.go
jupyter_test.go 3.8K
1package e2e23import (4 "strings"5 "testing"67 ghostty "go.mitchellh.com/libghostty"8)910// The notebook as in Jupyter, through libghostty: ▶ runs a cell, a11// click in a box edits it, the toolbar adds a cell, Shift+click selects12// two cells that Alt+Down moves together, and o folds an output.1314// boxText is the text inside a code cell's box on screen line l, or "":15// the box's left side is at column 10, after the bar and the prompt.16func boxText(l string) string {17 rs := []rune(l)18 const at = 1019 if len(rs) <= at || rs[at] != '│' && rs[at] != '┃' {20 return ""21 }22 rest := string(rs[at+1:])23 if j := strings.LastIndexAny(rest, "│┃"); j >= 0 {24 rest = rest[:j]25 }26 return strings.TrimSpace(rest)27}2829// rowOf is the screen row holding text, and the column it starts at.30func (s *session) rowOf(text string) (col, row int) {31 s.t.Helper()32 for y, l := range strings.Split(s.screen(), "\n") {33 if i := strings.Index(l, text); i >= 0 {34 return len([]rune(l[:i])), y35 }36 }37 s.t.Fatalf("no line holds %q:\n%s", text, s.screen())38 return 0, 039}4041// codes are the code in the cells' boxes, top to bottom.42func (s *session) codes() []string {43 var out []string44 for _, l := range strings.Split(s.screen(), "\n") {45 if t := boxText(l); t != "" {46 out = append(out, t)47 }48 }49 return out50}5152// An output is 012's grid: Enter works in it, Shift+arrows select,53// Ctrl+C copies, the palette's sort sorts it, a click moves the active54// cell, Enter shows it full-screen and Esc goes back a level at a time.55func TestNotebookOutputGrid(t *testing.T) {56 t.Parallel()57 s := startWith(t, options{cols: 100, rows: 40, startsOn: "EDIT", env: fakeNu}, "nu")58 clip := s.watchClipboard()59 s.keys("files = ls", "<ctrl+enter>")60 s.waitFor("Out[1]:")61 s.keys("<esc>", "<down>", "<enter>")62 s.waitFor("OUTPUT")63 s.keys("<shift+down>", "<shift+right>", "<ctrl+c>")64 s.eventually("the rows copied", func() bool { return clip.get() == "012\tfile\nCLAUDE.md\tfile" })65 s.waitFor("name to type, rows 1 to 2")66 s.keys("<esc>", "<right>", "<right>", "<ctrl+k>", "Sort sheet Z to A", "<enter>")67 s.eventually("sorted by size", func() bool {68 _, first := s.rowOf("README.md")69 _, last := s.rowOf("go.mod")70 return first < last && strings.Contains(s.line(first), " 1 README.md")71 })72 col, row := s.rowOf("demos")73 s.leftClick(col+1, row, 0)74 s.keys("<ctrl+c>")75 s.eventually("the cell clicked copied", func() bool { return clip.get() == "demos" })76 s.keys("<enter>")77 s.waitFor("files [1], full-screen")78 s.keys("<esc>")79 s.eventually("back in the window", func() bool {80 return !strings.Contains(s.screen(), "full-screen") || strings.Contains(s.screen(), "[1]:")81 })82 s.keys("<esc>")83 s.waitFor("NOTEBOOK")84}8586func TestNotebookMouseAndKeys(t *testing.T) {87 t.Parallel()88 s := startWith(t, options{cols: 100, rows: 40, startsOn: "EDIT", env: fakeNu}, "nu")89 s.keys("files = ls", "<esc>")90 s.waitFor("NOTEBOOK")91 _, row := s.rowOf("files = ls")92 s.leftClick(99, row, 0) // ▶93 s.waitFor("Out[1]:")9495 col, _ := s.rowOf("+ Add")96 s.leftClick(col, 1, 0) // the toolbar97 s.waitFor("[ ]:")98 s.keys("<enter>", "$files | get name | str join \", \"", "<esc>")99 s.waitFor("NOTEBOOK")100 _, row = s.rowOf("$files | get")101 s.leftClick(30, row, 0) // into the box: edit, the caret there102 s.waitFor("EDIT")103 s.keys("<esc>")104 s.waitFor("NOTEBOOK")105106 _, row = s.rowOf("files = ls")107 s.leftClick(5, row, ghostty.ModShift) // both cells108 s.waitFor("2 cells selected")109 s.keys("<alt+down>") // can't: the last cell is under them110 s.keys("<up>")111 s.keys("<alt+down>")112 s.eventually("the first cell moved down", func() bool {113 c := s.codes()114 return len(c) == 2 && strings.HasPrefix(c[0], "$files") && c[1] == "files = ls"115 })116 s.keys("<alt+up>", "<down>", "o")117 s.waitFor("output hidden")118 s.keys("o")119 s.eventually("the output shown again", func() bool { return !strings.Contains(s.screen(), "output hidden") })120}