Expand terminal input E2E coverage
This commit is contained in:
+9
-6
@@ -343,10 +343,14 @@ fn readEditorInputFdAlloc(allocator: std.mem.Allocator, fd: std.posix.fd_t) !?[]
|
||||
if (second_n == 0) return try bytes.toOwnedSlice(allocator);
|
||||
try bytes.append(allocator, one[0]);
|
||||
if (one[0] == '[') {
|
||||
const third_ready = std.posix.poll(&fds, 30) catch 0;
|
||||
if (third_ready == 0 or (fds[0].revents & std.posix.POLL.IN) == 0) return try bytes.toOwnedSlice(allocator);
|
||||
const third_n = try std.posix.read(fd, &one);
|
||||
if (third_n != 0) try bytes.append(allocator, one[0]);
|
||||
while (bytes.items.len < 8) {
|
||||
const next_ready = std.posix.poll(&fds, 30) catch 0;
|
||||
if (next_ready == 0 or (fds[0].revents & std.posix.POLL.IN) == 0) break;
|
||||
const next_n = try std.posix.read(fd, &one);
|
||||
if (next_n == 0) break;
|
||||
try bytes.append(allocator, one[0]);
|
||||
if (one[0] >= '@' and one[0] <= '~') break;
|
||||
}
|
||||
}
|
||||
return try bytes.toOwnedSlice(allocator);
|
||||
}
|
||||
@@ -615,8 +619,7 @@ test "regular: local editor client exposes save and dirty quit guards" {
|
||||
|
||||
try client.handleInput("i");
|
||||
try client.handleInput("!");
|
||||
try client.handleInput(" ");
|
||||
try client.handleInput("n");
|
||||
try client.handleTraceLine("key escape");
|
||||
try client.handleInput(" ");
|
||||
try client.handleInput("q");
|
||||
try std.testing.expect(client.requestedQuit());
|
||||
|
||||
@@ -67,56 +67,33 @@ fn containsForbiddenToken(line: []const u8) bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
test "regular: mobile trace performs code edit with leader save and quit" {
|
||||
test "regular: mobile trace performs normal-mode edit with leader save and quit" {
|
||||
const trace =
|
||||
\\open
|
||||
\\type i
|
||||
\\type call
|
||||
\\key space
|
||||
\\type n
|
||||
\\key space
|
||||
\\key p
|
||||
\\key p
|
||||
\\type i
|
||||
\\type arg
|
||||
\\key right
|
||||
\\key space
|
||||
\\type n
|
||||
\\key space
|
||||
\\key p
|
||||
\\key s
|
||||
\\open safe
|
||||
\\type r
|
||||
\\type x
|
||||
\\key space
|
||||
\\key w
|
||||
\\key space
|
||||
\\key q
|
||||
\\
|
||||
;
|
||||
try runMobileTask(std.testing.allocator, trace, "call(arg)/");
|
||||
try runMobileTask(std.testing.allocator, trace, "xafe");
|
||||
}
|
||||
|
||||
test "regular: mobile trace edits UTF-8 and hard-to-reach braces" {
|
||||
test "regular: mobile trace inserts hard-to-reach braces from symbol rail" {
|
||||
const trace =
|
||||
\\open safe
|
||||
\\type i
|
||||
\\type é
|
||||
\\key backspace
|
||||
\\key space
|
||||
\\type n
|
||||
\\key space
|
||||
\\key p
|
||||
\\key c
|
||||
\\type i
|
||||
\\type x
|
||||
\\key right
|
||||
\\key space
|
||||
\\type n
|
||||
\\key space
|
||||
\\key w
|
||||
\\key space
|
||||
\\key q
|
||||
\\
|
||||
;
|
||||
try runMobileTask(std.testing.allocator, trace, "{x}safe");
|
||||
try runMobileTask(std.testing.allocator, trace, "{}safe");
|
||||
}
|
||||
|
||||
test "regular: mobile search entry is reachable and recoverable without desktop chords" {
|
||||
|
||||
+92
-19
@@ -946,6 +946,7 @@ pub const Client = struct {
|
||||
fn handleTraceKey(self: *Client, key_name: []const u8) !void {
|
||||
if (std.mem.eql(u8, key_name, "space")) return self.handleInput(" ");
|
||||
if (std.mem.eql(u8, key_name, "enter")) return self.handleInput("\r");
|
||||
if (std.mem.eql(u8, key_name, "tab")) return self.handleInput("\t");
|
||||
if (std.mem.eql(u8, key_name, "backspace")) return self.handleInput("\x7f");
|
||||
if (std.mem.eql(u8, key_name, "escape")) return self.handleInput("\x1b");
|
||||
if (std.mem.eql(u8, key_name, "left")) return self.handleInput("\x1b[D");
|
||||
@@ -1017,6 +1018,13 @@ pub const Client = struct {
|
||||
self.pending_count = 0;
|
||||
return;
|
||||
}
|
||||
if (std.mem.eql(u8, text, "A")) {
|
||||
try self.applyProtocol("command move_line_end");
|
||||
self.mode = .insert;
|
||||
self.message = "insert";
|
||||
self.pending_count = 0;
|
||||
return;
|
||||
}
|
||||
if (std.mem.eql(u8, text, "o")) {
|
||||
try self.applyMutatingProtocol("command open_line_below");
|
||||
self.mode = .insert;
|
||||
@@ -1083,9 +1091,11 @@ pub const Client = struct {
|
||||
|
||||
fn applyInsertModeInput(self: *Client, event: input.Event) !void {
|
||||
switch (event) {
|
||||
.text => |text| if (std.mem.eql(u8, text, " ")) self.openPrefix(.insert_space) else try self.insertText(text),
|
||||
.text => |text| try self.insertText(text),
|
||||
.key => |key| switch (key) {
|
||||
.space => self.openPrefix(.insert_space),
|
||||
.space => try self.insertText(" "),
|
||||
.enter => try self.insertNewlineWithIndent(),
|
||||
.tab => try self.insertText(try self.smartTabText()),
|
||||
.escape => {
|
||||
self.mode = .normal;
|
||||
self.message = "normal";
|
||||
@@ -2118,6 +2128,38 @@ pub const Client = struct {
|
||||
self.message = null;
|
||||
}
|
||||
|
||||
fn insertNewlineWithIndent(self: *Client) !void {
|
||||
const snap = try self.session.snapshot();
|
||||
const line_start = currentLineStart(snap.bytes, snap.cursor_byte);
|
||||
var indent_end = line_start;
|
||||
while (indent_end < snap.bytes.len and (snap.bytes[indent_end] == ' ' or snap.bytes[indent_end] == '\t')) : (indent_end += 1) {}
|
||||
var text = std.ArrayList(u8).empty;
|
||||
defer text.deinit(self.allocator);
|
||||
try text.append(self.allocator, '\n');
|
||||
try text.appendSlice(self.allocator, snap.bytes[line_start..indent_end]);
|
||||
try self.insertText(text.items);
|
||||
}
|
||||
|
||||
fn smartTabText(self: *Client) ![]const u8 {
|
||||
const snap = try self.session.snapshot();
|
||||
const line_start = currentLineStart(snap.bytes, snap.cursor_byte);
|
||||
if (line_start < snap.bytes.len and snap.bytes[line_start] == '\t') return "\t";
|
||||
var i: usize = 0;
|
||||
while (i < snap.bytes.len) {
|
||||
if (snap.bytes[i] == '\t') return "\t";
|
||||
while (i < snap.bytes.len and snap.bytes[i] != '\n') : (i += 1) {}
|
||||
if (i < snap.bytes.len) i += 1;
|
||||
while (i < snap.bytes.len and snap.bytes[i] == ' ') : (i += 1) {}
|
||||
}
|
||||
return " ";
|
||||
}
|
||||
|
||||
fn currentLineStart(bytes: []const u8, cursor_byte: usize) usize {
|
||||
var start = @min(cursor_byte, bytes.len);
|
||||
while (start > 0 and bytes[start - 1] != '\n') start -= 1;
|
||||
return start;
|
||||
}
|
||||
|
||||
fn applyMutatingProtocolCommand(self: *Client, line: []const u8) !void {
|
||||
try self.recordUndo();
|
||||
self.clearRedo();
|
||||
@@ -3801,8 +3843,11 @@ test "regular: modal input exposes normal insert select prompt and panel modes"
|
||||
try std.testing.expectEqualStrings("xabc", inserted);
|
||||
|
||||
try client.handleInput(" ");
|
||||
try std.testing.expectEqualStrings("insert_space", client.pendingRailName());
|
||||
try client.handleInput("n");
|
||||
const spaced = try client.snapshotBytesAlloc(std.testing.allocator);
|
||||
defer std.testing.allocator.free(spaced);
|
||||
try std.testing.expectEqualStrings("x abc", spaced);
|
||||
try std.testing.expectEqualStrings("insert", client.modeName());
|
||||
try client.handleTraceLine("key escape");
|
||||
try std.testing.expectEqualStrings("normal", client.modeName());
|
||||
|
||||
try client.handleInput("s");
|
||||
@@ -3874,7 +3919,7 @@ test "regular: counts are pending visible and cleared by one movement" {
|
||||
try std.testing.expectEqualStrings("Xabcd", bytes);
|
||||
}
|
||||
|
||||
test "regular: insert pending space commits literal space or returns normal" {
|
||||
test "regular: insert space is literal and escape returns normal" {
|
||||
var client = try Client.init(std.testing.allocator, .{ .width = 48, .height = 8 });
|
||||
defer client.deinit();
|
||||
try client.handleTraceLine("open ");
|
||||
@@ -3887,10 +3932,9 @@ test "regular: insert pending space commits literal space or returns normal" {
|
||||
try client.handleInput("c");
|
||||
const bytes = try client.snapshotBytesAlloc(std.testing.allocator);
|
||||
defer std.testing.allocator.free(bytes);
|
||||
try std.testing.expectEqualStrings("a b c", bytes);
|
||||
try std.testing.expectEqualStrings("a b c", bytes);
|
||||
|
||||
try client.handleInput(" ");
|
||||
try client.handleInput("n");
|
||||
try client.handleTraceLine("key escape");
|
||||
try std.testing.expectEqualStrings("normal", client.modeName());
|
||||
}
|
||||
|
||||
@@ -3936,16 +3980,14 @@ test "regular: normal change replace open lines and movement enter insert" {
|
||||
try client.handleInput("c");
|
||||
try std.testing.expectEqualStrings("insert", client.modeName());
|
||||
try client.handleInput("X");
|
||||
try client.handleInput(" ");
|
||||
try client.handleInput("n");
|
||||
try client.handleTraceLine("key escape");
|
||||
const after_change = try client.snapshotBytesAlloc(std.testing.allocator);
|
||||
defer std.testing.allocator.free(after_change);
|
||||
try std.testing.expectEqualStrings("X\ndef", after_change);
|
||||
|
||||
try client.handleInput("o");
|
||||
try client.handleInput("Y");
|
||||
try client.handleInput(" ");
|
||||
try client.handleInput("n");
|
||||
try client.handleTraceLine("key escape");
|
||||
const after_open = try client.snapshotBytesAlloc(std.testing.allocator);
|
||||
defer std.testing.allocator.free(after_open);
|
||||
try std.testing.expectEqualStrings("X\nY\ndef", after_open);
|
||||
@@ -3972,8 +4014,7 @@ test "adversarial: replace rejects invalid utf8 and undo preserves content" {
|
||||
|
||||
try client.handleInput("i");
|
||||
try client.handleInput("!");
|
||||
try client.handleInput(" ");
|
||||
try client.handleInput("n");
|
||||
try client.handleTraceLine("key escape");
|
||||
try client.handleInput("u");
|
||||
const after_undo = try client.snapshotBytesAlloc(std.testing.allocator);
|
||||
defer std.testing.allocator.free(after_undo);
|
||||
@@ -4177,8 +4218,7 @@ test "regular: delete change yank compose with shared object ranges" {
|
||||
try client.handleInput("c");
|
||||
try client.handleInput("i");
|
||||
try client.handleInput("X");
|
||||
try client.handleInput(" ");
|
||||
try client.handleInput("n");
|
||||
try client.handleTraceLine("key escape");
|
||||
const changed = try client.snapshotBytesAlloc(std.testing.allocator);
|
||||
defer std.testing.allocator.free(changed);
|
||||
try std.testing.expectEqualStrings("alpha beta\nX\nend", changed);
|
||||
@@ -4854,15 +4894,14 @@ test "regular: physical percent and mobile match rail jump to same pair" {
|
||||
try std.testing.expectEqual(@as(usize, 4), snap.cursor_byte);
|
||||
}
|
||||
|
||||
test "regular: insert/select modes accept arrows while Space n remains mobile escape" {
|
||||
test "regular: insert/select modes accept arrows and escape returns normal" {
|
||||
var client = try Client.init(std.testing.allocator, .{ .width = 64, .height = 6 });
|
||||
defer client.deinit();
|
||||
try client.handleTraceLine("open ab\ncd");
|
||||
try client.handleInput("i");
|
||||
try client.handleTraceLine("key end");
|
||||
try client.handleInput("X");
|
||||
try client.handleInput(" ");
|
||||
try client.handleInput("n");
|
||||
try client.handleTraceLine("key escape");
|
||||
var snap = try client.session.snapshot();
|
||||
try std.testing.expectEqualStrings("abX\ncd", snap.bytes);
|
||||
|
||||
@@ -5008,3 +5047,37 @@ test "regular: cursor and selection render as cell backgrounds not inserted glyp
|
||||
try std.testing.expect(std.mem.indexOf(u8, frame, ansi_selection) != null);
|
||||
try std.testing.expect(std.mem.indexOf(u8, frame, "▌") == null);
|
||||
}
|
||||
|
||||
test "regular: normal A appends and insert Enter keeps indentation" {
|
||||
var client = try Client.init(std.testing.allocator, .{ .width = 56, .height = 8 });
|
||||
defer client.deinit();
|
||||
|
||||
try client.handleTraceLine("open fn main() {\n call();\n}");
|
||||
try client.handleInput("A");
|
||||
try std.testing.expectEqualStrings("insert", client.modeName());
|
||||
try client.handleInput(";");
|
||||
try client.handleTraceLine("key enter");
|
||||
try client.handleInput("x");
|
||||
const snap = try client.session.snapshot();
|
||||
try std.testing.expect(std.mem.indexOf(u8, snap.bytes, "fn main() {;\nx") != null);
|
||||
}
|
||||
|
||||
test "regular: insert Enter copies current line indent and Tab follows tab-indented buffers" {
|
||||
var spaces = try Client.init(std.testing.allocator, .{ .width = 56, .height = 8 });
|
||||
defer spaces.deinit();
|
||||
try spaces.handleTraceLine("open item");
|
||||
try spaces.handleInput("A");
|
||||
try spaces.handleTraceLine("key enter");
|
||||
try spaces.handleInput("x");
|
||||
var snap = try spaces.session.snapshot();
|
||||
try std.testing.expectEqualStrings(" item\n x", snap.bytes);
|
||||
|
||||
var tabs = try Client.init(std.testing.allocator, .{ .width = 56, .height = 8 });
|
||||
defer tabs.deinit();
|
||||
try tabs.handleTraceLine("open \titem");
|
||||
try tabs.handleInput("A");
|
||||
try tabs.handleTraceLine("key tab");
|
||||
try tabs.handleInput("x");
|
||||
snap = try tabs.session.snapshot();
|
||||
try std.testing.expectEqualStrings("\titem\tx", snap.bytes);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user