Every session is its own process, pty, temp directory, config home and fake services, so the tests and the golden screens' subtests call t.Parallel and run up to GOMAXPROCS at a time. TestScreens stays serial at the top, so TestStills reads the goldens it has just written under -update, and TestStressKeyLatency stays serial as it times keys. A screen is captured once it holds still and shows its golden, or after five seconds of not showing it: nu's highlighting can reach a busy screen after two captures matched, which parallel runs exposed.
- commit
- bfd41781fe92182fff11732aaabf34bc54e7f1cb
- parent
- 55928ef00d21eba0ed272fbcadf24a1ff6dbf6f1
- author
- Dami <dami.etoile@gmail.com>
- kind
- test risk score 1.3 of 3, Jev's estimate
| M | e2e/ | 13 | +++++++++++++ |
| M | e2e/ | 1 | + |
| M | e2e/ | 5 | +++++ |
| M | e2e/ | 5 | +++++ |
| M | e2e/ | 1 | + |
| M | e2e/ | 2 | ++ |
| M | e2e/ | 1 | + |
| M | e2e/ | 1 | + |
| M | e2e/ | 1 | + |
| M | e2e/ | 3 | +++ |
| M | e2e/ | 3 | +++ |
| M | e2e/ | 6 | ++++++ |
| M | e2e/ | 2 | ++ |
| M | e2e/ | 2 | ++ |
| M | e2e/ | 3 | +++ |
| M | e2e/ | 3 | +++ |
| M | e2e/ | 2 | ++ |
| M | e2e/ | 3 | +++ |
| M | e2e/ | 3 | +++ |
| M | e2e/ | 4 | ++++ |
| M | e2e/ | 7 | +++++++ |
| M | e2e/ | 2 | ++ |
| M | e2e/ | 4 | ++++ |
| M | e2e/ | 6 | ++++++ |
| M | e2e/ | 3 | +++ |
| M | e2e/ | 2 | ++ |
| M | e2e/ | 26 | +++++++++++++++++++++----- |
| M | e2e/ | 1 | + |
| M | e2e/ | 1 | + |
| M | e2e/ | 3 | +++ |
| M | e2e/ | 5 | +++++ |
| M | e2e/ | 1 | + |
| M | e2e/ | 2 | ++ |
| M | e2e/ | 1 | + |
| M | e2e/ | 1 | + |
| M | e2e/ | 1 | + |
| M | e2e/ | 1 | + |
| M | e2e/ | 1 | + |
| M | e2e/ | 1 | + |
@@ -61,6 +61,7 @@ func numRow(row int, nums ...string) string {
6161 }6262 6363 func TestFormulaRecalc(t *testing.T) {64+ t.Parallel()6465 s := start(t, "")6566 s.keys("10", "<enter>", "20", "<enter>", "=SUM(A1:A2)", "<enter>")6667 s.keys("<up>")@@ -73,6 +74,7 @@ func TestFormulaRecalc(t *testing.T) {
7374 }7475 7576 func TestPointModeBuildsFormula(t *testing.T) {77+ t.Parallel()7678 s := start(t, "")7779 s.keys("4", "<tab>", "6", "<tab>", "=", "<left>", "<left>")7880 s.waitFor("POINT")@@ -85,6 +87,7 @@ func TestPointModeBuildsFormula(t *testing.T) {
8587 }8688 8789 func TestShiftSelectionShowsStats(t *testing.T) {90+ t.Parallel()8891 s := start(t, "")8992 s.keys("1", "<enter>", "2", "<enter>", "3", "<enter>", "<ctrl+home>")9093 s.keys("<shift+down>", "<shift+down>")@@ -97,6 +100,7 @@ func TestShiftSelectionShowsStats(t *testing.T) {
97100 }98101 99102 func TestTextOverflow(t *testing.T) {103+ t.Parallel()100104 s := start(t, "")101105 s.keys("Quarterly revenue", "<enter>", "$1,200", "<enter>", "TRUE", "<enter>")102106 s.waitForLine(gridRow1, " 1 Quarterly revenue")@@ -105,6 +109,7 @@ func TestTextOverflow(t *testing.T) {
105109 }106110 107111 func TestInvalidFormulaCursor(t *testing.T) {112+ t.Parallel()108113 s := start(t, "")109114 s.keys("=SUM(A1", "<enter>")110115 s.waitFor("EDIT")@@ -117,6 +122,7 @@ func TestInvalidFormulaCursor(t *testing.T) {
117122 }118123 119124 func TestSaveQuitReopen(t *testing.T) {125+ t.Parallel()120126 dir := t.TempDir()121127 s := start(t, dir)122128 s.keys("Budget", "<enter>", "1200", "<enter>", "=A2*12", "<enter>")@@ -141,6 +147,7 @@ func TestSaveQuitReopen(t *testing.T) {
141147 }142148 143149 func TestResizeShowsMoreColumns(t *testing.T) {150+ t.Parallel()144151 s := start(t, "")145152 s.waitFor(" I")146153 if strings.Contains(s.line(3), " N") {@@ -151,6 +158,7 @@ func TestResizeShowsMoreColumns(t *testing.T) {
151158 }152159 153160 func TestCopyPasteAdjustsReferences(t *testing.T) {161+ t.Parallel()154162 s := start(t, "")155163 clip := s.watchClipboard()156164 s.keys("2", "<tab>", "=A1*10", "<enter>", "3", "<enter>", "<ctrl+home>", "<right>", "<ctrl+c>")@@ -174,6 +182,7 @@ func TestCopyPasteAdjustsReferences(t *testing.T) {
174182 }175183 176184 func TestUndoRedo(t *testing.T) {185+ t.Parallel()177186 s := start(t, "")178187 s.keys("10", "<enter>", "=A1+1", "<enter>", "<up>", "<up>", "<shift+down>", "<delete>")179188 s.waitForLine(gridRow1, " 1")@@ -188,6 +197,7 @@ func TestUndoRedo(t *testing.T) {
188197 }189198 190199 func TestInsertRowRewritesFormulas(t *testing.T) {200+ t.Parallel()191201 s := start(t, "")192202 s.keys("1", "<enter>", "2", "<enter>", "=SUM(A1:A2)", "<enter>")193203 s.keys("<up>", "<up>", "<shift+space>", "<ctrl+alt+=>")@@ -202,6 +212,7 @@ func TestInsertRowRewritesFormulas(t *testing.T) {
202212 }203213 204214 func TestPasteTSVFillsCells(t *testing.T) {215+ t.Parallel()205216 s := start(t, "")206217 s.paste("Item\tCost\nRent\t1450\nFood\t=B2/2\n")207218 s.waitFor("Pasted 6 cells at A1:B3")@@ -209,6 +220,7 @@ func TestPasteTSVFillsCells(t *testing.T) {
209220 }210221 211222 func TestFillAndAbsoluteReferences(t *testing.T) {223+ t.Parallel()212224 s := start(t, "")213225 s.keys("2", "<enter>", "3", "<enter>", "4", "<enter>", "<ctrl+home>", "<right>")214226 s.keys("=A1*A1", "<left>", "<left>", "<f4>")@@ -222,6 +234,7 @@ func TestFillAndAbsoluteReferences(t *testing.T) {
222234 // Click handling is covered by the unit tests; this checks the program223235 // actually turned on mouse reporting in the terminal.224236 func TestMouseTrackingEnabled(t *testing.T) {237+ t.Parallel()225238 s := start(t, "")226239 s.eventually("mouse tracking enabled", func() bool {227240 s.mu.Lock()@@ -9,6 +9,7 @@ import (
99 // they come from, a cell in the way makes it #REF! until cleared, and the1010 // file keeps only the formula, which spills again when it opens.1111 func TestArraySpill(t *testing.T) {12+ t.Parallel()1213 dir := t.TempDir()1314 s := start(t, dir)1415 s.keys("pear", "<enter>", "apple", "<enter>", "fig", "<enter>")@@ -28,6 +28,7 @@ func insertChart(s *session) {
2828 }2929 3030 func TestInsertEditMoveDeleteChart(t *testing.T) {31+ t.Parallel()3132 dir := t.TempDir()3233 s := start(t, dir)3334 spending(s)@@ -76,6 +77,7 @@ func TestInsertEditMoveDeleteChart(t *testing.T) {
7677 }7778 7879 func TestDragChart(t *testing.T) {80+ t.Parallel()7981 s := start(t, "")8082 spending(s)8183 insertChart(s)@@ -93,6 +95,7 @@ func TestDragChart(t *testing.T) {
9395 }9496 9597 func TestKittyImages(t *testing.T) {98+ t.Parallel()9699 s := startWith(t, options{graphics: true})97100 spending(s)98101 insertChart(s)@@ -142,6 +145,7 @@ func TestKittyImages(t *testing.T) {
142145 }143146 144147 func TestLinksAndErrorMarks(t *testing.T) {148+ t.Parallel()145149 s := start(t, "")146150 s.keys("https://example.com/docs", "<enter>", `=HYPERLINK("example.org", "Example")`, "<enter>", "=1/0", "<enter>", "<up>")147151 s.waitFor("#DIV/0! Division by zero in 1/0")@@ -174,6 +178,7 @@ func TestLinksAndErrorMarks(t *testing.T) {
174178 // its legend, saves and reopens it, and turns it into a scatter with a175179 // trend line.176180 func TestChartOptions(t *testing.T) {181+ t.Parallel()177182 dir := t.TempDir()178183 s := start(t, dir)179184 spending(s)@@ -11,6 +11,7 @@ import (
1111 // Problems in the config file show on the context line; 012 starts1212 // anyway, with defaults for what's wrong.1313 func TestConfigWarnings(t *testing.T) {14+ t.Parallel()1415 s := startWith(t, options{config: "theme = Dracula\nbogus = 1\nchart-images = maybe\n"})1516 s.waitFor(`Config: config:2: unknown key "bogus" (and 1 more; run 012 config)`)1617 if !strings.Contains(s.html(), "background:#1e1f29") {@@ -29,6 +30,7 @@ func openTheme(s *session) {
2930 // The theme picker previews the highlighted theme, Esc restores the one3031 // there was, and Enter keeps and saves it, so the next start has it.3132 func TestThemePicker(t *testing.T) {33+ t.Parallel()3234 cfg := t.TempDir()3335 s := startWith(t, options{configDir: cfg})3436 openTheme(s)@@ -55,6 +57,7 @@ func TestThemePicker(t *testing.T) {
5557 // A .env with the key next to the sheet is never read: JEV stays off,5658 // and the context line says how to store the key instead.5759 func TestDotEnvNotRead(t *testing.T) {60+ t.Parallel()5861 srv, requests := fakeTypeSafe(t)5962 dir := t.TempDir()6063 os.WriteFile(filepath.Join(dir, ".env"), []byte("TYPESAFE_API_KEY=test-key\nTYPESAFE_BASE_URL="+srv.URL+"\n"), 0o600)@@ -70,6 +73,7 @@ func TestDotEnvNotRead(t *testing.T) {
7073 // 012 config set-key stores the key (here in the tests' stand-in for the7174 // credential store); the next start finds it there and JEV works.7275 func TestSetKeyThenJEV(t *testing.T) {76+ t.Parallel()7377 srv, requests := fakeTypeSafe(t)7478 cfg := t.TempDir()7579 cmd := exec.Command(binPath, "config", "set-key")@@ -90,6 +94,7 @@ func TestSetKeyThenJEV(t *testing.T) {
9094 // File > Settings > JEV API key takes the key masked, stores it and turns9195 // JEV on at once, after one test call with it.9296 func TestAPIKeyPrompt(t *testing.T) {97+ t.Parallel()9398 srv, _ := fakeTypeSafe(t)9499 cfg := t.TempDir()95100 s := startWith(t, options{configDir: cfg, env: []string{"TYPESAFE_BASE_URL=" + srv.URL}})@@ -15,6 +15,7 @@ import (
1515 // binary is built with -tags crashtest, where F12 panics where1616 // O12_CRASH_TEST says (internal/ui/crashtest.go).1717 func TestPanicKeepsWork(t *testing.T) {18+ t.Parallel()1819 for _, where := range []string{"update", "command", "view"} {1920 t.Run(where, func(t *testing.T) {2021 dir := t.TempDir()@@ -24,6 +24,7 @@ func writeTable(t *testing.T, dir, name string, n int) {
2424 }2525 2626 func TestFreezeFilterAndSort(t *testing.T) {27+ t.Parallel()2728 dir := t.TempDir()2829 writeTable(t, dir, "data.012", 60)2930 s := start(t, dir, "data.012")@@ -63,6 +64,7 @@ func TestFreezeFilterAndSort(t *testing.T) {
6364 }6465 6566 func TestSortBarAndFillHandle(t *testing.T) {67+ t.Parallel()6668 s := start(t, "")6769 s.keys("Month", "<tab>", "Sales", "<enter>", "Jan", "<tab>", "30", "<enter>", "Feb", "<tab>", "10", "<enter>")6870 s.keys("<up>")@@ -9,6 +9,7 @@ import (
99 // too long for the context line ends in … with an F1 chip, that F11010 // shows all of it in a box, and that a key closes the box.1111 func TestLongErrorOnF1(t *testing.T) {12+ t.Parallel()1213 s := startWith(t, options{cols: 60, rows: 16})1314 s.keys("='Quarterly results by region 26'!A1+1", "<enter>", "<up>")1415 s.waitFor("#REF!")@@ -31,6 +31,7 @@ func (s *session) linkTable(name string) {
3131 }3232 3333 func TestFollowFileAsItGrows(t *testing.T) {34+ t.Parallel()3435 dir := t.TempDir()3536 log := filepath.Join(dir, "app.csv")3637 writeText(t, log, "level,ms\ninfo,12\n")@@ -6,6 +6,7 @@ import (
66 )77 88 func TestTypeDateAndApplyCurrency(t *testing.T) {9+ t.Parallel()910 s := start(t, "")1011 s.keys("9/26/2026", "<enter>", "2026-09-27", "<enter>", "14:30", "<enter>")1112 s.waitForLine(gridRow1, numRow(1, "9/26/2026"))@@ -30,6 +31,7 @@ func TestTypeDateAndApplyCurrency(t *testing.T) {
3031 }3132 3233 func TestFormatsSurviveSaveAndReopen(t *testing.T) {34+ t.Parallel()3335 dir := t.TempDir()3436 s := start(t, dir)3537 s.keys("1450", "<enter>", "<up>", "<ctrl+shift+1>", "<ctrl+i>")@@ -49,6 +51,7 @@ func TestFormatsSurviveSaveAndReopen(t *testing.T) {
4951 // A column's format travels when the column is copied, and formulas5052 // reading its blanks take it at once.5153 func TestColumnFormatTravelsWithCopy(t *testing.T) {54+ t.Parallel()5255 s := start(t, "")5356 s.keys("5", "<enter>", "7", "<enter>", "<ctrl+home>", "<right>", "=C9*2", "<enter>")5457 s.waitForLine(gridRow1, numRow(1, "5", "0"))@@ -7,6 +7,7 @@ import (
77 // Typing =su shows matching functions under the formula bar; Tab inserts88 // one, and the context line then shows its signature.99 func TestAutocompleteAndSignature(t *testing.T) {10+ t.Parallel()1011 s := start(t, "")1112 s.keys("2", "<enter>", "3", "<enter>", "=sum")1213 s.waitFor("│ SUM ")@@ -21,6 +22,7 @@ func TestAutocompleteAndSignature(t *testing.T) {
2122 // A named range is defined from the selection, used in a formula, found2223 // by Go to, and follows inserted rows.2324 func TestNamedRanges(t *testing.T) {25+ t.Parallel()2426 s := start(t, "")2527 s.keys("10", "<enter>", "20", "<enter>", "=SUM(Pair)", "<enter>")2628 s.waitFor("#NAME?")@@ -41,6 +43,7 @@ func TestNamedRanges(t *testing.T) {
4143 4244 // Alt+, jumps to what a formula reads; Esc comes back.4345 func TestTracePrecedents(t *testing.T) {46+ t.Parallel()4447 s := start(t, "")4548 s.keys("1", "<enter>", "2", "<enter>", "=A1+A2", "<enter>", "<up>")4649 s.keys("<alt+,>")@@ -55,6 +55,7 @@ func writeText(t *testing.T, name, text string) {
5555 }5656 5757 func TestOpenXLSXFromCommandLine(t *testing.T) {58+ t.Parallel()5859 dir := t.TempDir()5960 writeXLSX(t, filepath.Join(dir, "q3.xlsx"))6061 s := start(t, dir, "q3.xlsx")@@ -87,6 +88,7 @@ func TestOpenXLSXFromCommandLine(t *testing.T) {
8788 }8889 8990 func TestOpenCSVFromCommandLine(t *testing.T) {91+ t.Parallel()9092 dir := t.TempDir()9193 writeText(t, filepath.Join(dir, "prices.csv"), "\xEF\xBB\xBFItem;Price;Date\nTea;$3.50;9/26/2026\nCake;4;2026-09-27\n")9294 s := start(t, dir, "prices.csv")@@ -108,6 +110,7 @@ func TestOpenCSVFromCommandLine(t *testing.T) {
108110 }109111 110112 func TestOpenMissingImport(t *testing.T) {113+ t.Parallel()111114 dir := t.TempDir()112115 out, err := runBinary(dir, "nope.xlsx")113116 if err == nil || !strings.Contains(out, "nope.xlsx: no such file") {@@ -129,6 +132,7 @@ func importDir(t *testing.T, dir string) {
129132 }130133 131134 func TestImportPickerLists(t *testing.T) {135+ t.Parallel()132136 dir := t.TempDir()133137 importDir(t, dir)134138 s := start(t, dir)@@ -181,6 +185,7 @@ func runBinary(dir string, args ...string) (string, error) {
181185 }182186 183187 func TestImportProgressAndCancel(t *testing.T) {188+ t.Parallel()184189 s := start(t, "")185190 slowCSV(s)186191 s.waitFor("WAIT")@@ -194,6 +199,7 @@ func TestImportProgressAndCancel(t *testing.T) {
194199 // file goes: new sheets after the one shown (every sheet of an .xlsx),195200 // undone as one step, or in place of the sheet shown.196201 func TestImportLocation(t *testing.T) {202+ t.Parallel()197203 dir := t.TempDir()198204 importDir(t, dir)199205 s := start(t, dir)@@ -59,6 +59,7 @@ func fakeTypeSafe(t *testing.T) (*httptest.Server, *atomic.Int32) {
5959 }6060 6161 func TestJEVFunctionsAgainstFakeService(t *testing.T) {62+ t.Parallel()6263 srv, requests := fakeTypeSafe(t)6364 s := startWith(t, options{env: []string{"TYPESAFE_API_KEY=test-key", "TYPESAFE_BASE_URL=" + srv.URL}})6465 s.keys("The box arrived crushed", "<enter>")@@ -79,6 +80,7 @@ func TestJEVFunctionsAgainstFakeService(t *testing.T) {
7980 }8081 8182 func TestJEVOffWithoutKey(t *testing.T) {83+ t.Parallel()8284 s := start(t, "")8385 s.keys(`=JEV.TEST("x", "Q")`, "<enter>", "<up>")8486 s.waitFor("JEV functions need an API key")@@ -53,6 +53,7 @@ func (s *session) codes() []string {
5353 // Ctrl+C copies, the palette's sort sorts it, a click moves the active5454 // cell, Enter shows it full-screen and Esc goes back a level at a time.5555 func TestNotebookOutputGrid(t *testing.T) {56+ t.Parallel()5657 s := startWith(t, options{cols: 100, rows: 40, startsOn: "EDIT", env: fakeNu}, "nu")5758 clip := s.watchClipboard()5859 s.keys("files = ls", "<ctrl+enter>")@@ -83,6 +84,7 @@ func TestNotebookOutputGrid(t *testing.T) {
8384 }8485 8586 func TestNotebookMouseAndKeys(t *testing.T) {87+ t.Parallel()8688 s := startWith(t, options{cols: 100, rows: 40, startsOn: "EDIT", env: fakeNu}, "nu")8789 s.keys("files = ls", "<esc>")8890 s.waitFor("NOTEBOOK")@@ -12,6 +12,7 @@ import (
1212 // terminal without it sends Shift+Enter as Enter and Ctrl+I as Tab, and1313 // 012 does what those do.1414 func TestKittyKeyboard(t *testing.T) {15+ t.Parallel()1516 t.Run("on", func(t *testing.T) {1617 s := start(t, "")1718 s.eventually("key events on", func() bool {@@ -47,6 +48,7 @@ func TestKittyKeyboard(t *testing.T) {
4748 // let go; without key events Space deselects the chart and starts an4849 // entry, as it always has.4950 func TestHoldSpaceOnChart(t *testing.T) {51+ t.Parallel()5052 t.Run("on", func(t *testing.T) {5153 s := start(t, "")5254 spending(s)@@ -82,6 +84,7 @@ func TestHoldSpaceOnChart(t *testing.T) {
8284 // the highlighted theme, until it's let go; with a search typed, or8385 // without key events, Space is typed.8486 func TestHoldSpaceInThemePicker(t *testing.T) {87+ t.Parallel()8588 t.Run("on", func(t *testing.T) {8689 s := start(t, "")8790 openTheme(s)@@ -10,6 +10,7 @@ import (
1010 // The trip plan's layout draws as a table, the notes wrapped over their1111 // rows, and comes back from its file as it was.1212 func TestLayoutDrawsAndSurvivesSave(t *testing.T) {13+ t.Parallel()1314 dir := t.TempDir()1415 s := start(t, dir)1516 tripPlan(s)@@ -41,6 +42,7 @@ func TestLayoutDrawsAndSurvivesSave(t *testing.T) {
4142 // through; the total's middle alignment and the blue outline survive a4243 // save.4344 func TestMergeEntryAndAlignment(t *testing.T) {45+ t.Parallel()4446 dir := t.TempDir()4547 s := start(t, dir)4648 coloredPlan(s)@@ -74,6 +76,7 @@ func TestMergeEntryAndAlignment(t *testing.T) {
7476 // Dragging a row number's corner makes the row taller; double-clicking7577 // it fits the row again.7678 func TestDragRowHeight(t *testing.T) {79+ t.Parallel()7780 s := start(t, "")7881 s.keys("Tall", "<enter>")7982 s.mouse(ghostty.MouseActionMotion, ghostty.MouseButtonUnknown, 5, gridRow1, 0)@@ -143,6 +143,7 @@ func startLive(t *testing.T, o options) (*session, *agent) {
143143 }144144 145145 func TestLiveAgentSuggestsAndThePersonAccepts(t *testing.T) {146+ t.Parallel()146147 s, a := startLive(t, options{})147148 read := a.tool("read_range", map[string]any{"ref": "A1:A3"})148149 if got := fmt.Sprint(read["values"]); got != "[[40] [2] [42]]" {@@ -173,6 +174,7 @@ func TestLiveAgentSuggestsAndThePersonAccepts(t *testing.T) {
173174 }174175 175176 func TestLiveAgentAsksThePerson(t *testing.T) {177+ t.Parallel()176178 s, a := startLive(t, options{})177179 answer := make(chan map[string]any, 1)178180 go func() { answer <- a.tool("ask", map[string]any{"message": "Overwrite A1:A2?"}) }()@@ -25,6 +25,7 @@ func germanBudget(s *session) {
2525 // formulas typed the German way, saves them as en-US writes them, and2626 // undoes back to en-US.2727 func TestLocaleSwitch(t *testing.T) {28+ t.Parallel()2829 dir := t.TempDir()2930 s := start(t, dir)3031 germanBudget(s)@@ -62,6 +63,7 @@ func TestLocaleSwitch(t *testing.T) {
6263 // The config's locale, here from LANG, is the default of new sheets;6364 // a file saved without a locale of its own opens in the reader's.6465 func TestLocaleFromLANG(t *testing.T) {66+ t.Parallel()6567 dir := t.TempDir()6668 s := startWith(t, options{dir: dir, env: []string{"LANG=de_DE.UTF-8"}})6769 s.keys("1,5", "<enter>", "=A1*2", "<enter>")@@ -82,6 +84,7 @@ func TestLocaleFromLANG(t *testing.T) {
8284 // with German names, and a formula that doesn't parse names German's8385 // separator.8486 func TestLocaleNamesAndErrors(t *testing.T) {87+ t.Parallel()8588 s := start(t, t.TempDir())8689 germanBudget(s)8790 s.keys("<ctrl+g>", "D1", "<enter>", "3. Okt. 2026", "<enter>")@@ -28,6 +28,7 @@ func recordTotals(s *session) {
2828 }2929 3030 func TestMacroRecordAndReplay(t *testing.T) {31+ t.Parallel()3132 dir := t.TempDir()3233 s := start(t, dir, "book.012")3334 recordTotals(s)@@ -73,6 +74,7 @@ func TestMacroRecordAndReplay(t *testing.T) {
7374 }7475 7576 func TestMacroFromAnotherComputerAsks(t *testing.T) {77+ t.Parallel()7678 dir := t.TempDir()7779 s := start(t, dir, "book.012")7880 recordTotals(s)@@ -102,6 +104,7 @@ func TestMacroFromAnotherComputerAsks(t *testing.T) {
102104 // TestMacroRecordsTheSortBar records the choices made in a dialog and103105 // replays them.104106 func TestMacroRecordsTheSortBar(t *testing.T) {107+ t.Parallel()105108 dir := t.TempDir()106109 s := start(t, dir, "book.012")107110 s.keys("Month", "<tab>", "Sales", "<enter>", "Jan", "<tab>", "10", "<enter>", "Feb", "<tab>", "30", "<enter>", "Mar", "<tab>", "20", "<enter>")@@ -39,6 +39,7 @@ func (s *session) wheel(col, row, clicks int) {
3939 func colX(c int) int { return 6 + c*10 + 2 }4040 4141 func TestMouseClickDragAndShiftClick(t *testing.T) {42+ t.Parallel()4243 s := start(t, "")4344 s.leftClick(colX(1), gridRow1+2, 0)4445 s.waitForName("B3")@@ -57,6 +58,7 @@ func TestMouseClickDragAndShiftClick(t *testing.T) {
5758 }5859 5960 func TestMouseClickWhileTypingAndFormulaReference(t *testing.T) {61+ t.Parallel()6062 s := start(t, "")6163 s.keys("40", "<enter>", "2", "<enter>", "=")6264 s.leftClick(colX(0), gridRow1, 0)@@ -75,6 +77,7 @@ func TestMouseClickWhileTypingAndFormulaReference(t *testing.T) {
7577 }7678 7779 func TestMouseResizeAndWheel(t *testing.T) {80+ t.Parallel()7881 s := start(t, "")7982 border := 6 + 10 - 18083 s.drag([2]int{border, 3}, [2]int{border + 4, 3}, [2]int{border + 8, 3})@@ -90,6 +93,7 @@ func TestMouseResizeAndWheel(t *testing.T) {
9093 }9194 9295 func TestMouseDoubleClickEdits(t *testing.T) {96+ t.Parallel()9397 s := start(t, "")9498 s.keys("hello", "<enter>")9599 s.leftClick(colX(0), gridRow1, 0)@@ -24,6 +24,7 @@ func needNu(t *testing.T) {
2424 }2525 2626 func TestNotebookWithNu(t *testing.T) {27+ t.Parallel()2728 needNu(t)2829 dir := t.TempDir()2930 s := startWith(t, options{dir: dir, startsOn: "EDIT"}, "nu", "book.012")@@ -53,6 +54,7 @@ func TestNotebookWithNu(t *testing.T) {
5354 // assigns, runs with the real nu: its output is the second's, and a5455 // later cell reads the variable.5556 func TestNotebookStatementsWithNu(t *testing.T) {57+ t.Parallel()5658 needNu(t)5759 dir := t.TempDir()5860 os.WriteFile(filepath.Join(dir, "big.txt"), []byte(strings.Repeat("x", 3000)), 0o644)@@ -79,6 +81,7 @@ func TestNotebookStatementsWithNu(t *testing.T) {
7981 }8082 8183 func TestNotebookSendsToSheetWithNu(t *testing.T) {84+ t.Parallel()8285 needNu(t)8386 s := startWith(t, options{startsOn: "EDIT"}, "nu")8487 s.keys("n = [[k]; [1] [2] [3]]", "<ctrl+enter>")@@ -96,6 +99,7 @@ func TestNotebookSendsToSheetWithNu(t *testing.T) {
9699 // With the real nu, a cell being written is highlighted by nu's shapes97100 // once typing pauses, and Tab completes what only nu knows: a flag.98101 func TestNotebookHighlightsAndCompletesWithNu(t *testing.T) {102+ t.Parallel()99103 needNu(t)100104 s := startWith(t, options{startsOn: "EDIT"}, "nu")101105 s.keys("ls | where size > 1kb | sort-by size")@@ -109,6 +113,7 @@ func TestNotebookHighlightsAndCompletesWithNu(t *testing.T) {
109113 // command's help, with its page in nushell's docs as a link. On a110114 // cell's $name, the line says the cell and its output's shape.111115 func TestNotebookHoverWithNu(t *testing.T) {116+ t.Parallel()112117 needNu(t)113118 s := startWith(t, options{cols: 120, rows: 30, startsOn: "EDIT"}, "nu")114119 s.keys("n = [[k v]; [1 a] [2 b] [3 c]]", "<shift+enter>")@@ -133,6 +138,7 @@ func TestNotebookHoverWithNu(t *testing.T) {
133138 // Data > Shell from a workbook makes a Notebook tab; $selection is134139 // the range selected on the sheet shown before it.135140 func TestNotebookReadsSelection(t *testing.T) {141+ t.Parallel()136142 needNu(t)137143 s := start(t, "")138144 s.keys("n", "<enter>", "3", "<enter>", "4", "<enter>", "<up>", "<up>", "<up>", "<shift+down>", "<shift+down>", "<shift+down>")@@ -150,6 +156,7 @@ func TestNotebookReadsSelection(t *testing.T) {
150156 // A 200-character pipeline at 80 columns is on the screen whole, wrapped151157 // before its pipes, while it's edited and once it isn't.152158 func TestNotebookLongPipeline(t *testing.T) {159+ t.Parallel()153160 s := startWith(t, options{cols: 80, rows: 24, startsOn: "EDIT", env: fakeNu}, "nu")154161 var b strings.Builder155162 b.WriteString("ls")@@ -8,6 +8,7 @@ import (
88 )99 1010 func TestNotes(t *testing.T) {11+ t.Parallel()1112 dir := t.TempDir()1213 s := start(t, dir)1314 s.keys("Rent", "<tab>", "1450", "<enter>", "<up>", "<right>")@@ -38,6 +39,7 @@ func TestNotes(t *testing.T) {
3839 }3940 4041 func TestProtectedRange(t *testing.T) {42+ t.Parallel()4143 s := start(t, "")4244 s.keys("Price", "<enter>", "10", "<enter>", "<up>", "<shift+up>")4345 s.waitForName("A1:A2")@@ -41,6 +41,7 @@ const sizesNu = "[[name size]; [a 2kb] [b 10b] [c 5mb]]"
4141 // A table goes through sheet: a selection is sent back, and nu gets it4242 // with its types, file sizes as file sizes.4343 func TestSheetSendsSelection(t *testing.T) {44+ t.Parallel()4445 s := startSheet(t, "5.0 MB", "let t = "+sizesNu+" | sheet; print ($t | get size | describe); $t | to nuon")4546 s.keys("<shift+down>", "<shift+down>", "<shift+right>")4647 s.waitFor("send A1:B3 as NUON")@@ -56,6 +57,7 @@ func TestSheetSendsSelection(t *testing.T) {
5657 // sheet --send sheet gives back all of ls's table on Ctrl+Q, whatever5758 // is selected.5859 func TestSheetSendSheet(t *testing.T) {60+ t.Parallel()5961 s := startSheet(t, "b.txt", "cd (mktemp -d); 'x' | save a.txt; 'yyy' | save b.txt; "+6062 "let t = ls | sheet --send sheet; print ($t | columns | to nuon); "+6163 "print (($t | select name type size) == (ls | select name type size)); print ($t | length)")@@ -70,6 +72,7 @@ func TestSheetSendSheet(t *testing.T) {
7072 7173 // Quitting without sending raises sheet's error, with 012's reason.7274 func TestSheetNotSent(t *testing.T) {75+ t.Parallel()7376 s := startSheet(t, "5.0 MB", "try { "+sizesNu+" | sheet } catch {|e| print $'caught: ($e.msg)' }")7477 s.keys("<ctrl+q>", "d")7578 out, code := s.finish()@@ -80,6 +83,7 @@ func TestSheetNotSent(t *testing.T) {
8083 8184 // sheet view shows the table and returns nothing.8285 func TestSheetView(t *testing.T) {86+ t.Parallel()8387 s := startSheet(t, "5.0 MB", sizesNu+" | sheet view | describe")8488 s.keys("<ctrl+q>")8589 s.waitFor("You have unsaved changes.")@@ -72,6 +72,7 @@ const lsNUON = "[[name, size, modified]; [CLAUDE.md, 1646b, 2026-09-27T11:27:31-
7272 // an edit is made, and quitting sends it on standard output, which gets7373 // nothing else.7474 func TestPipeEditAndSend(t *testing.T) {75+ t.Parallel()7576 s := startWith(t, options{piped: true, stdin: lsNUON, env: []string{"TZ=America/Denver"}}, "--pipe")7677 s.waitFor("Read 4 rows of NUON from standard input")7778 s.waitFor("4.2 kB")@@ -92,6 +93,7 @@ func TestPipeEditAndSend(t *testing.T) {
9293 // TestPipeSendSelectionAsCSV sends a selection, in the format --to9394 // asks for.9495 func TestPipeSendSelectionAsCSV(t *testing.T) {96+ t.Parallel()9597 s := startWith(t, options{piped: true, stdin: lsNUON}, "--pipe", "--to", "csv")9698 s.waitFor("4.2 kB")9799 s.keys("<shift+down>", "<shift+down>", "<shift+right>")@@ -108,6 +110,7 @@ func TestPipeSendSelectionAsCSV(t *testing.T) {
108110 // TestPipeSendSelectionWithoutAsking: --send selection makes Ctrl+Q109111 // send the selection at once.110112 func TestPipeSendSelectionWithoutAsking(t *testing.T) {113+ t.Parallel()111114 s := startWith(t, options{piped: true, stdin: lsNUON}, "--pipe", "--send", "selection", "--to", "csv")112115 s.waitFor("Ctrl+Q send the sheet as CSV")113116 s.keys("<shift+down>", "<shift+right>")@@ -123,6 +126,7 @@ func TestPipeSendSelectionWithoutAsking(t *testing.T) {
123126 // whole sheet, whatever is selected; File > Quit without sending still124127 // sends nothing.125128 func TestPipeSendSheetWithoutAsking(t *testing.T) {129+ t.Parallel()126130 s := startWith(t, options{piped: true, stdin: "a,b\n1,2\n3,4\n"}, "--pipe", "--send", "sheet")127131 s.waitFor("Read 3 rows of CSV from standard input")128132 s.keys("<shift+down>")@@ -146,6 +150,7 @@ func TestPipeSendSheetWithoutAsking(t *testing.T) {
146150 147151 // TestPipeQuitWithoutSending exits with status 1 and writes nothing.148152 func TestPipeQuitWithoutSending(t *testing.T) {153+ t.Parallel()149154 s := startWith(t, options{piped: true, stdin: "a,b\n1,2\n"}, "--pipe")150155 s.waitFor("Read 2 rows of CSV from standard input")151156 s.keys("<ctrl+q>", "d")@@ -158,6 +163,7 @@ func TestPipeQuitWithoutSending(t *testing.T) {
158163 // TestStdinOnly: 012 - reads standard input and writes nothing to159164 // standard output.160165 func TestStdinOnly(t *testing.T) {166+ t.Parallel()161167 s := startWith(t, options{piped: true, stdin: `[{"a": 1, "b": "x"}]`}, "-")162168 s.waitFor("Read 2 rows of JSON from standard input")163169 s.keys("<ctrl+q>")@@ -33,6 +33,7 @@ func pivotByCategory(s *session) {
3333 }3434 3535 func TestPivotTable(t *testing.T) {36+ t.Parallel()3637 dir := t.TempDir()3738 s := start(t, dir)3839 pivotByCategory(s)@@ -80,6 +81,7 @@ func TestPivotTable(t *testing.T) {
8081 }8182 8283 func TestFrequencyTable(t *testing.T) {84+ t.Parallel()8385 s := start(t, "")8486 inventory(s)8587 s.keys("<right>", "<down>", "<alt+shift+f>")@@ -100,6 +102,7 @@ func TestFrequencyTable(t *testing.T) {
100102 }101103 102104 func TestPivotRenameAndColumnSubtotals(t *testing.T) {105+ t.Parallel()103106 s := start(t, "")104107 pivotByCategory(s)105108 // R renames the value on the context line.@@ -14,6 +14,7 @@ import (
1414 // rejects, a conditional format added from the panel, and all of it1515 // saved and opened again.1616 func TestRules(t *testing.T) {17+ t.Parallel()1718 dir := t.TempDir()1819 tasksFile(t, dir)1920 s := start(t, dir, "tasks.012")@@ -74,6 +75,7 @@ func TestRules(t *testing.T) {
7475 // data bar added from the panel, and cells cut to another sheet taking7576 // their rules along.7677 func TestBarsIconsAndChips(t *testing.T) {78+ t.Parallel()7779 dir := t.TempDir()7880 scoresFile(t, dir)7981 s := start(t, dir, "scores.012")@@ -45,20 +45,21 @@ func TestScreens(t *testing.T) {
4545 }4646 for _, sc := range screens {4747 t.Run(sc.name, func(t *testing.T) {48+ t.Parallel()4849 s := startScreen(t, sc)4950 sc.setup(s)50- got := s.stableHTML()5151 path := filepath.Join(dir, sc.name+".html")52+ want, err := os.ReadFile(path)53+ if err != nil && !*update {54+ t.Fatalf("missing golden (run make screens): %v", err)55+ }56+ got := s.settledHTML(string(want))5257 if *update {5358 if err := os.WriteFile(path, []byte(got), 0o644); err != nil {5459 t.Fatal(err)5560 }5661 return5762 }58- want, err := os.ReadFile(path)59- if err != nil {60- t.Fatalf("missing golden (run make screens): %v", err)61- }6263 if got != string(want) {6364 actual := filepath.Join(t.TempDir(), sc.name+".html")6465 os.WriteFile(actual, []byte(got), 0o644)@@ -153,6 +154,21 @@ func (s *session) stableHTML() string {
153154 return ""154155 }155156 157+// settledHTML is the screen once it has settled: stable, and showing158+// want (the golden) if it comes to within waitTimeout. Some of a screen159+// arrives after it first holds still, such as nu's highlighting, which160+// a busy machine delays past a capture or two; a screen that differs161+// from its golden for good fails, or is rewritten under -update, once162+// the wait is over.163+func (s *session) settledHTML(want string) string {164+ s.t.Helper()165+ got := s.stableHTML()166+ for deadline := time.Now().Add(waitTimeout); got != want && time.Now().Before(deadline); {167+ got = s.stableHTML()168+ }169+ return got170+}171+156172 // writeGallery renders all goldens into gallery.html (git-ignored), once157173 // with each reference palette.158174 func writeGallery(dir string) error {@@ -41,6 +41,7 @@ func tabX(t *testing.T, s *session, name string) int {
4141 }4242 4343 func TestSheets(t *testing.T) {44+ t.Parallel()4445 dir := t.TempDir()4546 s := start(t, dir, "book.012")4647 summary(s)@@ -102,6 +103,7 @@ func TestSheets(t *testing.T) {
102103 // Hiding a sheet takes it out of the tabs and the file keeps it hidden;103104 // View > Hidden sheets shows it again, and undo hides it once more.104105 func TestHideSheets(t *testing.T) {106+ t.Parallel()105107 dir := t.TempDir()106108 s := start(t, dir, "book.012")107109 hideSummary(s)@@ -137,6 +139,7 @@ func TestHideSheets(t *testing.T) {
137139 // Typing a sheet's name in a formula offers it; after it, arrows point138140 // into that sheet.139141 func TestSheetNameSuggestions(t *testing.T) {142+ t.Parallel()140143 s := start(t, "")141144 summary(s)142145 s.keys("<ctrl+pgup>", "<f5>", "D1", "<enter>")@@ -13,6 +13,7 @@ import (
1313 // and the context-line confirmations.1414 1515 func TestAltFOpensFileMenuAndSaves(t *testing.T) {16+ t.Parallel()1617 dir := t.TempDir()1718 s := start(t, dir)1819 s.keys("42", "<enter>", "<alt+f>")@@ -30,6 +31,7 @@ func TestAltFOpensFileMenuAndSaves(t *testing.T) {
3031 }3132 3233 func TestMenuSetsColumnWidth(t *testing.T) {34+ t.Parallel()3335 s := start(t, "")3436 s.keys("<alt+o>", "c", "<enter>", "20", "<enter>")3537 s.waitFor("READY")@@ -37,6 +39,7 @@ func TestMenuSetsColumnWidth(t *testing.T) {
3739 }3840 3941 func TestPaletteSearchAndRun(t *testing.T) {42+ t.Parallel()4043 s := start(t, "")4144 s.keys("<ctrl+k>")4245 s.waitFor("Search the menus")@@ -49,6 +52,7 @@ func TestPaletteSearchAndRun(t *testing.T) {
4952 }5053 5154 func TestRightClickColumnMenu(t *testing.T) {55+ t.Parallel()5256 s := start(t, "")5357 // Right-click column C's header: it selects the column and opens the5458 // column menu under the mouse.@@ -71,6 +75,7 @@ func TestRightClickColumnMenu(t *testing.T) {
7175 }7276 7377 func TestQuitAsksAboutUnsavedChanges(t *testing.T) {78+ t.Parallel()7479 s := start(t, "")7580 s.keys("1", "<enter>", "<ctrl+q>")7681 s.waitFor("You have unsaved changes.")@@ -73,6 +73,7 @@ func (s *session) linkSource(name string) {
7373 }7474 7575 func TestLinkedSource(t *testing.T) {76+ t.Parallel()7677 dir := t.TempDir()7778 writeTrips(t, dir, tripRows)7879 s := start(t, dir)@@ -80,6 +80,7 @@ func serveSSH(t *testing.T, top, served string, flags ...string) (string, []stri
8080 // screen, typing, resizing, saving inside the served directory, a8181 // refused escape, and quitting closing the connection.8282 func TestServeOverSSH(t *testing.T) {83+ t.Parallel()8384 top := t.TempDir()8485 served := filepath.Join(top, "served")8586 os.Mkdir(served, 0o755)@@ -112,6 +113,7 @@ func TestServeOverSSH(t *testing.T) {
112113 // ssh -t host book.012 opens the file from the served directory; a name113114 // outside it is refused before 012 starts.114115 func TestServeOpensFileFromCommandLine(t *testing.T) {116+ t.Parallel()115117 top := t.TempDir()116118 served := filepath.Join(top, "served")117119 os.Mkdir(served, 0o755)@@ -12,6 +12,7 @@ import (
1212 // written: its rows reach the output and the sheet it was sent to as1313 // lines are appended, and Stop ends it, keeping them.1414 func TestNotebookStreamWithNu(t *testing.T) {15+ t.Parallel()1516 needNu(t)1617 dir := t.TempDir()1718 logFile := filepath.Join(dir, "app.log")@@ -9,6 +9,7 @@ import (
99 // grows as rows are typed below it, renames its column in formulas when1010 // its header changes, and is saved with the file.1111 func TestTables(t *testing.T) {12+ t.Parallel()1213 dir := t.TempDir()1314 writeTable(t, dir, "stock.012", 4) // Item, Qty over A1:B5; Qty 1 2 3 01415 s := start(t, dir, "stock.012")@@ -9,6 +9,7 @@ import (
99 // to one from a list, and Evaluate formula steps into a reference and1010 // back out.1111 func TestTraceAndEvaluate(t *testing.T) {12+ t.Parallel()1213 s := start(t, "")1314 s.keys("<ctrl+g>", "C1", "<enter>")1415 s.keys("2", "<enter>", "=C1*3", "<enter>", "=C2+C1", "<enter>")@@ -6,6 +6,7 @@ import "testing"
66 // to the last row and three-letter columns, with row numbers widening to77 // fit.88 func TestMillionRows(t *testing.T) {9+ t.Parallel()910 s := start(t, "")1011 s.keys("5", "<enter>", "7", "<enter>", "<right>", "<up>", "<up>", "=SUM(A:A)", "<enter>")1112 s.waitFor("12")