const std = @import("std"); const tui = @import("tui.zig"); // Mobile ergonomic acceptance tasks guard against desktop-keyboard regressions. // req: input/001, input/002, input/003, input/004, input/005, testing/001, testing/002, testing/003, testing/004 test { _ = runMobileTask; } const AcceptanceError = error{ ForbiddenDesktopInput, DirectTraceCommand, }; fn runMobileTask(allocator: std.mem.Allocator, trace: []const u8, expected_saved: []const u8) !void { try assertMobileOnlyTrace(trace); const result = try tui.runTrace(allocator, .{ .width = 64, .height = 5 }, trace); defer result.deinit(allocator); try std.testing.expect(result.quit); try std.testing.expectEqualStrings(expected_saved, result.saved_bytes.?); } fn assertMobileOnlyTrace(trace: []const u8) !void { var lines = std.mem.splitScalar(u8, trace, '\n'); while (lines.next()) |line| { if (line.len == 0) continue; if (std.mem.startsWith(u8, line, "insert ") or std.mem.eql(u8, line, "save") or std.mem.eql(u8, line, "quit") or std.mem.eql(u8, line, "left") or std.mem.eql(u8, line, "right") or std.mem.eql(u8, line, "backspace")) { return AcceptanceError.DirectTraceCommand; } if (containsForbiddenToken(line)) return AcceptanceError.ForbiddenDesktopInput; } } fn containsForbiddenToken(line: []const u8) bool { const forbidden = [_][]const u8{ "key escape", "ctrl", "control", "alt", "option", "cmd", "command-key", "function", "key f1", "key f2", "key f3", "key f4", "key f5", "key f6", "key f7", "key f8", "key f9", "key f10", "key f11", "key f12", }; for (forbidden) |token| { if (std.mem.indexOf(u8, line, token) != null) return true; } return false; } test "regular: mobile trace performs code 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 \\key space \\key w \\key space \\key q \\ ; try runMobileTask(std.testing.allocator, trace, "call(arg)/"); } test "regular: mobile trace edits UTF-8 and hard-to-reach braces" { 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"); } test "regular: mobile search entry is reachable and recoverable without desktop chords" { const trace = \\open abc \\key space \\key s \\ ; try assertMobileOnlyTrace(trace); var client = try tui.Client.init(std.testing.allocator, .{ .width = 64, .height = 4 }); defer client.deinit(); var lines = std.mem.splitScalar(u8, trace, '\n'); while (lines.next()) |line| { if (line.len != 0) try client.handleTraceLine(line); } const frame = try client.render(std.testing.allocator); defer std.testing.allocator.free(frame); try std.testing.expect(std.mem.indexOf(u8, frame, "search: f current file") != null); try std.testing.expect(!client.quit); try std.testing.expectError(tui.Error.NothingSaved, client.saved()); } test "adversarial: acceptance traces reject direct editor commands" { try std.testing.expectError(AcceptanceError.DirectTraceCommand, assertMobileOnlyTrace("open abc\ninsert x\n")); try std.testing.expectError(AcceptanceError.DirectTraceCommand, assertMobileOnlyTrace("open abc\nsave\n")); try std.testing.expectError(AcceptanceError.DirectTraceCommand, assertMobileOnlyTrace("open abc\nquit\n")); } test "adversarial: acceptance traces reject forbidden desktop chords" { try std.testing.expectError(AcceptanceError.ForbiddenDesktopInput, assertMobileOnlyTrace("open abc\nkey escape\n")); try std.testing.expectError(AcceptanceError.ForbiddenDesktopInput, assertMobileOnlyTrace("open abc\nctrl-s\n")); try std.testing.expectError(AcceptanceError.ForbiddenDesktopInput, assertMobileOnlyTrace("open abc\nkey f1\n")); }