╭ changed ──────╮ │src/test/ui.zig│ ╰───────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 4 const Commits = @import("../ui/Repo/Commits.zig"); │ │ 5 const Events = @import("../ui/Repo/Events.zig"); │ │ 6 const xit = @import("xit"); │ │ 7 + │ │ 8 +test "diff windows span the net changes across commits" { │ │ 9 + const io = std.testing.io; │ │10 + const allocator = std.testing.allocator; │ │11 + const Diff = Commits.Diff; │ │12 + const opts: xit.repo.RepoOpts(.xit) = .{ .is_test = true }; │ │13 + var temp = std.testing.tmpDir(.{}); │ │14 + defer temp.cleanup(); │ │15 + const path = try temp.dir.realPathFileAlloc(io, ".", allocator); │ │16 + defer allocator.free(path); │ │17 + var repo = try xit.repo.Repo(.xit, opts).init(io, allocator, .{ .path = path }); │ │18 + defer repo.deinit(io, allocator); │ │19 + var arena = std.heap.ArenaAllocator.init(allocator); │ │20 + defer arena.deinit(); │ │21 │ │22 + // the shared base is absent from the diff │ │23 + { │ │24 + const file = try repo.core.work_dir.createFile(io, "shared.txt", .{}); │ │25 + defer file.close(io); │ │26 + try file.writeStreamingAll(io, "shared\n"); │ │27 + } │ │28 + try repo.add(io, allocator, &.{"shared.txt"}); │ │29 + const base = try repo.commit(io, allocator, .{ .message = "base" }); │ │30 + │ │31 + // changes from earlier commits must survive into the combined diff │ │32 + var head = base; │ │33 + for (0..Diff.page_size + 1) |i| { │ │34 + var name_buf: [32]u8 = undefined; │ │35 + const name = try std.fmt.bufPrint(&name_buf, "file-{d:0>2}.txt", .{i}); │ │36 + { │ │37 + const file = try repo.core.work_dir.createFile(io, name, .{}); │ │38 + defer file.close(io); │ │39 + try file.writeStreamingAll(io, "added\n"); │ │40 + } │ │41 + try repo.add(io, allocator, &.{name}); │ │42 + head = try repo.commit(io, allocator, .{ .message = "add file" }); │ │43 + } │ │44 + const first = try Diff.render(.xit, opts, io, allocator, arena.allocator(), &repo, &base, head, 0, ""); │ │45 + try std.testing.expectEqual(Diff.page_size, first.hunks.len); │ │46 + try std.testing.expect(first.start == 0 and first.has_more); │ │47 + try std.testing.expectEqualStrings("file-00.txt", first.hunks[0].path orelse return error.MissingPath); │ │48 + const last = try Diff.render(.xit, opts, io, allocator, arena.allocator(), &repo, &base, head, Diff.page_size, ""); │ │49 + try std.testing.expectEqual(1, last.hunks.len); │ │50 + try std.testing.expect(last.start > 0 and !last.has_more); │ │51 + try std.testing.expectEqualStrings("file-10.txt", last.hunks[0].path orelse return error.MissingPath); │ │52 + const filtered = try Diff.render(.xit, opts, io, allocator, arena.allocator(), &repo, &base, head, 0, "file-00.txt"); │ │53 + try std.testing.expectEqual(1, filtered.hunks.len); │ │54 + try std.testing.expect(!filtered.has_more); │ │55 + │ │56 + // the standalone pane links to the next fork diff window in both clients │ │57 + const id = "11111111111111111111111111111111"; │ │58 + const data = Diff{ .route = .{ .fork = .{ .identity = "alice/project", .id = id } }, .window = first }; │ │59 + for ([_]bool{ true, false }) |terminal| { │ │60 + var session = ui.Session{ .arena = &arena, .page_arena = &arena, .is_terminal = terminal }; │ │61 + session.data.current_page = ui.RoutablePage.forkDiffRoute("alice/project", id, 0, "") orelse return error.BadRoute; │ │62 + var view = try Diff.View.init(allocator, &data, &session); │ │63 + defer view.deinit(allocator); │ │64 + const focus = view.getFocus(); │ │65 + try view.build(allocator, .{ .min_size = .{ .width = null, .height = null }, .max_size = .{ .width = 100, .height = 30 } }, focus);│ │66 + const box = &view.scroll.child.box; │ │67 + try std.testing.expectEqual(first.hunks.len * 2 + 1, box.children.count()); │ │68 + const file = ui.crossPageLink(focus, box.children.keys()[0], session.data) orelse return error.MissingFileLink; │ │69 + try std.testing.expectEqualStrings("file-00.txt", file.fork_diff.path.slice()); │ │70 + const next = ui.crossPageLink(focus, box.children.keys()[box.children.count() - 1], session.data) orelse return error.MissingLink; │ │71 + try std.testing.expectEqual(Diff.page_size, next.fork_diff.start); │ │72 + } │ │73 +} │ │74 + │ │75 // the "next" row at the bottom of the commits list must be recognized as a │ │76 // cross-page link (so a click navigates), exactly like the diff pane's "next". │ │77 test "commits list next row is a cross-page link" { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │88 .ref_or_oid = .object, │ │89 .ref_or_oid_value = oid0, │ │90 .commits = &.{ │ │24 - .{ .oid = oid0, .date = "2024-01-01", .message = "first", .hunks = &.{}, .window_start = 0, .has_prev = false, .has_more = false },│ │91 + .{ .oid = oid0, .date = "2024-01-01", .message = "first", .window = .{} }, │ │92 }, │ │93 .next_start = next_oid, │ │94 .header = try Commits.Header.init(arena.allocator(), .object, oid0), │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯