diff options
Diffstat (limited to 'tests.zig')
| -rw-r--r-- | tests.zig | 164 |
1 files changed, 125 insertions, 39 deletions
@@ -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)"); |
