summaryrefslogtreecommitdiff
path: root/tests.zig
diff options
context:
space:
mode:
Diffstat (limited to 'tests.zig')
-rw-r--r--tests.zig164
1 files changed, 125 insertions, 39 deletions
diff --git a/tests.zig b/tests.zig
index c389b8cf..504dad4d 100644
--- a/tests.zig
+++ b/tests.zig
@@ -16,6 +16,16 @@ const config = @import("config");
// Cols/rows are 1-based screen coords. Button 0=left, 1=middle, 2=right.
// A drag is press at start + release at end (with different coords).
+// Row index (0-based) of the first screen line containing `needle`, or null.
+fn rowOf(text: []const u8, needle: []const u8) ?usize {
+ var it = std.mem.splitScalar(u8, text, '\n');
+ var i: usize = 0;
+ while (it.next()) |line| : (i += 1) {
+ if (std.mem.indexOf(u8, line, needle) != null) return i;
+ }
+ return null;
+}
+
pub fn main(init: std.process.Init) !void {
const gpa = init.gpa;
const args = try init.minimal.args.toSlice(init.arena.allocator());
@@ -42,54 +52,110 @@ pub fn main(init: std.process.Init) !void {
// 3. Default is NORMAL mode: the tag shows "nm" (helix-style block cursor).
try hs.expectContains("nm /", "default tag is not 'nm' (normal mode not default)");
- // 4. Enter switches to TTY mode from normal and forwards keystrokes to the
- // shell. (Esc no longer reaches tty; Enter is the tty-enter key.)
+ // 3b. ls-on-open: a fresh shell greets with its directory listing. The e2e app
+ // boots in the project dir, so a known file shows up as command output
+ // (visible even in normal mode — only prompt rows are hidden).
+ try hs.expectWaitFor("build.zig", 8000, "new terminals did not auto-ls their directory on open");
+
+ // 3c. Normal-mode TAB = acme middle-click "execute" the word under the cursor.
+ // Drop a one-line executable, echo its path onto body row 0 (clear homes the
+ // cursor first), left-click the path to enter normal + pin the cursor on it,
+ // then TAB. It runs the word in place (no new pane) -> the script's marker.
+ try hs.send("\x02"); // normal -> tty (Ctrl-b)
+ try hs.pump(600);
+ try hs.send("printf '#!/bin/sh\\necho ZZTABRAN\\n' > /tmp/zztabcmd; chmod +x /tmp/zztabcmd; clear; echo /tmp/zztabcmd\r");
+ try hs.pump(1200);
+ try hs.send("\x1b[<0;8;2M\x1b[<0;8;2m"); // left-click the path on body row 0 -> normal + pin
+ try hs.pump(600);
+ try hs.send("\t"); // TAB: execute "/tmp/zztabcmd"
+ try hs.expectWaitFor("ZZTABRAN", 8000, "normal-mode Tab did not execute the word under the cursor");
+ try hs.pump(400);
+
+ // 3d. OUTPUT PADDING: a command composed (typed as an overlay) far below the
+ // prompt runs BELOW where it was typed, not back up at the prompt. The app
+ // pads the shell down with newlines to max(last-edit row, prompt row); the
+ // padding prompts are hidden in text mode, so the output flows under the
+ // composed line. Clear the pane (prompt -> row 0), type a command on a low
+ // row whose OUTPUT marker (PAD13Z) differs from its source (echo PAD...),
+ // run it, and assert the output row is BELOW the typed row. (Screen is 30x100
+ // from step 2; terms[0] is the ~15-row top-left pane.)
+ try hs.send("\x02"); // normal -> tty
+ try hs.pump(400);
+ try hs.send("clear\r"); // prompt back to row 0
+ try hs.pump(900);
+ try hs.send("\x02"); // tty -> normal
+ try hs.pump(400);
+ try hs.send("\x1b[<0;5;10M\x1b[<0;5;10m"); // left-click a low blank row (screen row 9) -> normal + pin
+ try hs.pump(400);
+ try hs.send("i"); // insert
+ try hs.pump(400);
+ try hs.send("echo PAD$((6+7))Z"); // overlay; output is "PAD13Z"
+ try hs.pump(600);
+ try hs.send("\x1b"); // -> normal
+ try hs.pump(400);
+ try hs.send("\x1b[<1;5;10M\x1b[<33;21;10M\x1b[<1;21;10m"); // middle-drag-select the line -> run (padded)
+ try hs.expectWaitFor("PAD13Z", 8000, "padded command did not run");
+ try hs.pump(600);
+ {
+ const text = try hs.screenText();
+ defer gpa.free(text);
+ const typed = rowOf(text, "echo PAD") orelse {
+ hs.dump("composed line vanished before the padding check");
+ return error.ExpectFailed;
+ };
+ const outp = rowOf(text, "PAD13Z") orelse {
+ hs.dump("padded output not found on screen");
+ return error.ExpectFailed;
+ };
+ if (outp <= typed) {
+ hs.dump("output did not land below the composed command (padding failed)");
+ return error.ExpectFailed;
+ }
+ // and it should land RIGHT under the composed line (tight alignment), not
+ // somewhere far below — the hidden padding prompts fill the gap above it.
+ if (outp - typed > 2) {
+ hs.dump("padded output landed too far below the composed line (misaligned)");
+ return error.ExpectFailed;
+ }
+ }
+ // tidy up: drop the lingering overlay so it doesn't ride the scrollback into
+ // later steps (cursor is on the typed row; x selects it, d clears the run).
+ try hs.send("x");
+ try hs.pump(300);
+ try hs.send("d");
+ try hs.pump(300);
+
+ // 4. Ctrl-b toggles raw TTY mode from normal and forwards keystrokes to the
+ // shell. tty is a hidden toggle (Ctrl-b), off the normal editing workflow.
const marker = "PARDES_E2E";
- try hs.send("\r"); // normal -> tty (Enter)
+ try hs.send("\x02"); // normal -> tty (Ctrl-b)
try hs.pump(600);
try hs.send("echo " ++ marker ++ "\r");
try hs.expectWaitFor(marker, 8000, "tty mode did not forward the marker to the shell");
- try hs.expectContains("sy /", "no 'sy' indicator after Enter (tty mode)");
+ try hs.expectContains("sy /", "no 'sy' indicator after Ctrl-b (tty mode)");
- // 4b. Enter-to-tty POSITIONING (the start-vs-end distinction). Still in tty:
- // clear to a clean screen, type input WITHOUT running it, go to normal,
- // navigate the modal cursor to the last char of the typed text, then
- // Enter. enterTty uses ghostty's promptClickMove to move the shell's real
- // cursor there, so the next typed char inserts BEFORE that last char.
- // Running it yields "ABCDEXF" — proving the shell cursor was repositioned
- // mid-input. A no-op Enter (cursor stays at end) would append -> "ABCDEFX".
- try hs.send("clear\r");
- try hs.pump(800);
- try hs.send("echo ABCDEF"); // typed input, NOT run (no \r)
- try hs.pump(800);
- try hs.send("\x1b[<0;3;2M\x1b[<0;3;2m"); // left-click body row 0, col 0 (col 3 = past gutter) -> normal
- try hs.pump(600);
- try hs.send("$"); // modal: end of row 0 (lands on 'F')
+ // 4b. Ctrl-b toggles back: tty -> normal. Send Ctrl-b again and confirm the
+ // tag flips off "sy" (raw tty) back to "nm" (normal).
+ try hs.send("\x02"); // tty -> normal (Ctrl-b)
try hs.pump(600);
- try hs.send("\r"); // Enter: normal -> tty, shell cursor moved onto 'F'
+ try hs.expectContains("nm /", "Ctrl-b did not toggle tty back to normal");
+ try hs.send("\x02"); // normal -> tty again for the prompt-hiding step
try hs.pump(600);
- try hs.send("X\r"); // insert 'X' before 'F', then run -> "ABCDEXF"
- try hs.expectWaitFor("ABCDEXF", 10000, "Enter did not reposition the shell cursor mid-input (no ABCDEXF)");
- try hs.pump(600);
- try hs.expectNotContains("ABCDEFX", "Enter left the shell cursor at the end (appended X -> ABCDEFX)");
- // 5. Prompt hiding. In tty mode a command line + its output are both visible.
- // Normal mode shows the real terminal (prompts visible); INSERT mode is the
- // acme compose surface, hiding prompt/command rows but keeping the output.
- // Run a command, left-click to normal, press `i` to enter insert, then
- // check: output present, prompt + command absent.
+ // 5. Prompt hiding in TEXT mode. tty shows the real terminal (command line +
+ // output). BOTH normal and insert hide the prompt/command rows (the acme
+ // compose surface) while keeping output. Run a command in tty, left-click
+ // to NORMAL, and check: output present, the command's prompt row hidden.
try hs.send("echo CMDHIDEZZ\r");
try hs.expectWaitFor("CMDHIDEZZ", 8000, "tty mode did not run the command");
try hs.pump(800); // let tty-mode frames settle
- try hs.send("\x1b[<0;6;3M\x1b[<0;6;3m"); // left-click body -> normal
- try hs.pump(600);
- try hs.send("i"); // normal -> insert (prompts now hidden)
+ try hs.send("\x1b[<0;6;3M\x1b[<0;6;3m"); // left-click body -> normal (prompts now hidden)
try hs.pump(600);
- try hs.expectContains("CMDHIDEZZ", "command output not shown in insert mode");
- // The prompt row (with the typed command) is hidden in insert mode. We check
- // "echo CMDHIDEZZ" (specific to this pane's prompt row) rather than
- // "goblin@" because other panes in normal mode show their own prompts.
- try hs.expectNotContains("echo CMDHIDEZZ", "command line still visible in insert mode (prompt row not hidden)");
+ try hs.expectContains("CMDHIDEZZ", "command output not shown in normal mode");
+ // The prompt row (with the typed command) is hidden in normal mode now too. We
+ // check "echo CMDHIDEZZ" (this pane's prompt row) rather than "goblin@" since a
+ // raw-tty pane elsewhere could show its own prompt.
+ try hs.expectNotContains("echo CMDHIDEZZ", "command line still visible in normal mode (prompt row not hidden)");
// 6. Insert-mode editing + middle-click execute. Click a blank row to pin the
// cursor at text col 0, type a computed echo, then middle-select those 15
@@ -103,16 +169,36 @@ pub fn main(init: std.process.Init) !void {
try hs.pump(600); // let the run build
try hs.send("\x1b[<1;3;14M\x1b[<33;17;14M\x1b[<1;17;14m"); // middle-select it
try hs.expectWaitFor("Q7Z", 8000, "middle-click did not execute the selected typed text");
- // leave insert -> normal so the next step can reach tty via Enter
+ // leave insert -> normal so the next step can reach tty via Ctrl-b
try hs.send("\x1b");
try hs.pump(400);
+ // 6b. FOLLOW OUTPUT: new tty output scrolls the pane to the bottom, so a result
+ // shows even after you've scrolled up. Fill the pane, wheel UP off the
+ // bottom (LN40 disappears), then run a command — the pane snaps back and the
+ // new marker is visible. Done in tty mode so the active pane follows too.
+ try hs.send("\x02"); // normal -> tty
+ try hs.pump(400);
+ try hs.send("clear; for i in $(seq 1 40); do echo LN$i; done\r");
+ try hs.expectWaitFor("LN40", 8000, "filler output never appeared");
+ try hs.pump(500);
+ {
+ var w: usize = 0;
+ while (w < 30) : (w += 1) try hs.send("\x1b[<64;5;5M"); // wheel UP over the pane
+ }
+ try hs.pump(500);
+ try hs.expectNotContains("LN40", "wheel-up did not scroll the pane off the bottom");
+ try hs.send("echo ZZFOLLOW\r");
+ try hs.expectWaitFor("ZZFOLLOW", 8000, "new output did not scroll the pane back to the bottom");
+ try hs.send("\x02"); // tty -> normal (step 7 re-enters via Ctrl-b)
+ try hs.pump(400);
+
// 7. Right-click a directory opens (acme "look") a terminal there and ls's
// it. Make a dir with a unique entry, then `clear` and echo its path so the
// path lands as contiguous output on body row 0. Right-click (no drag) on
// it: the new terminal lists the dir, so the unique entry appears only if
// the directory was actually opened.
- try hs.send("\r"); // normal -> tty (Enter)
+ try hs.send("\x02"); // normal -> tty (Ctrl-b)
try hs.pump(700);
try hs.send("rm -rf /tmp/pardesE2E; mkdir -p /tmp/pardesE2E/ZZUNIQDIR; clear; echo /tmp/pardesE2E\r");
try hs.pump(2000); // let it run + settle
@@ -180,7 +266,7 @@ pub fn main(init: std.process.Init) !void {
// intro) appears on screen. Middle-click is button 1 (SGR: <1;...M).
// Tag row 0 is the first row of any pane; "Tutor" sits at the end of the
// tag. Click somewhere in the tag's right half to hit it.
- try hs.send("\x1b[<1;45;1M\x1b[<1;45;1m"); // middle-click the tag row (pane 0)
+ try hs.send("\x1b[<1;48;1M\x1b[<1;48;1m"); // middle-click "Tutor" in the tag row (file pane's "nm " prefix shifts it +3)
try hs.expectWaitFor("PARDES TUTOR", 10000, "middle-click 'Tutor' did not spawn the tutor pane");
try hs.expectContains("tmux + vi + acme", "tutor pane content is wrong (no tagline)");