╭ ui.zig ───────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
1 const std = @import("std"); 2 const ui = @import("../ui.zig"); 3 const Commits = @import("../ui/Repo/Commits.zig"); 4 const SubHeader = @import("../ui/Repo/Commits/SubHeader.zig"); 5 6 // the "next" row at the bottom of the commits list must be recognized as a 7 // cross-page link (so a click navigates), exactly like the diff pane's "next". 8 test "commits list next row is a cross-page link" { 9 const allocator = std.testing.allocator; 10 11 var arena = std.heap.ArenaAllocator.init(allocator); 12 defer arena.deinit(); 13 const identity = "alice/ziglings"; 14 const oid0 = "1111111111111111111111111111111111111111"; 15 const next_oid = "2222222222222222222222222222222222222222"; 16 17 const data = Commits{ 18 .identity = identity, 19 .ref_or_oid = .object, 20 .ref_or_oid_value = oid0, 21 .commits = &.{ 22 .{ .oid = oid0, .date = "2024-01-01", .message = "first", .hunks = &.{}, .window_start = 0, .has_prev = false, .has_more = false }, 23 }, 24 .next_start = next_oid, 25 .sub_header = try SubHeader.init(arena.allocator(), .object, oid0), 26 }; 27 28 var session = ui.Session{ .arena = &arena, .page_arena = &arena, .is_terminal = true }; 29 session.data.current_page = ui.RoutablePage.repoCommitsRoute(identity, .object, oid0, 0).?; 30 31 var view = try Commits.View.init(allocator, &data, &session); 32 defer view.deinit(allocator); 33 34 const root_focus = view.getFocus(); 35 try view.build(allocator, .{ 36 .min_size = .{ .width = null, .height = null }, 37 .max_size = .{ .width = 120, .height = 60 }, 38 }, root_focus); 39 40 // the "next" row is the last child of the list box: the view's outer box 41 // holds the sub-header then the list/diff split, whose first child is the 42 // list scroll. 43 const content = &view.box.children.values()[1].widget.box; 44 const lb = &content.children.values()[0].widget.scroll.child.box; 45 const next_id = lb.children.keys()[lb.children.count() - 1]; 46 47 const route = ui.crossPageLink(root_focus, next_id, session.data); 48 try std.testing.expect(route != null); 49 } 50 51 // a ref name with a '/' is url-encoded in the route, so it survives the 52 // '/'-delimited url round-trip as a single segment (kind + value) rather than 53 // being mis-parsed as extra path. 54 test "encoded ref name survives the commits url round-trip" { 55 var arena = std.heap.ArenaAllocator.init(std.testing.allocator); 56 defer arena.deinit(); 57 const RP = ui.RoutablePage; 58 59 // the route layer holds the value already url-encoded ("feature%2Ffoo"). 60 const route = RP.repoCommitsRoute("alice/ziglings", .branch, "feature%2Ffoo", 0).?; 61 const url = try route.urlAlloc(&arena); 62 try std.testing.expectEqualStrings("/repo/alice/ziglings/commits/branch/feature%2Ffoo", url); 63 64 // parsing it back yields the same route, and the ref splits out intact. 65 const parsed = RP.fromUrl(url, null); 66 try std.testing.expect(parsed != null); 67 const ref = RP.repoCommitsRef(parsed.?.repo_commits.name.slice()); 68 try std.testing.expectEqual(RP.RefOrOid.branch, ref.ref_or_oid.?); 69 try std.testing.expectEqualStrings("feature%2Ffoo", ref.value); 70 } 71