[OCaml] Mobile-friendly clone of cgit.
1
(** Commit list pagination arithmetic, exercised through the router.
2
3
The orphan-absorption rule: a page absorbs up to ten commits beyond the
4
page size when they would otherwise form a near-empty final page. *)
5
6
open Test_helpers
7
8
let with_repository_of ~commits ~page_size test =
9
with_temp_directory "ogit-pagination" (fun root ->
10
let name = "project" in
11
let path = Filename.concat root name in
12
Unix.mkdir path 0o755;
13
ignore (git [ "-C"; path; "init"; "-q"; "-b"; "main" ]);
14
ignore (git [ "-C"; path; "config"; "user.name"; "Test" ]);
15
ignore (git [ "-C"; path; "config"; "user.email"; "t@t.invalid" ]);
16
for index = 1 to commits do
17
Out_channel.with_open_text (Filename.concat path "f.txt") (fun ch ->
18
Printf.fprintf ch "revision %d\n" index);
19
ignore (git [ "-C"; path; "add"; "." ]);
20
ignore
21
(git [ "-C"; path; "commit"; "-q"; "-m"; Printf.sprintf "c%d" index ])
22
done;
23
let config =
24
Ogit.Config.
25
{
26
default with
27
git_project_root = root;
28
commits_max_displayed = page_size;
29
}
30
in
31
let request = Dream.test (Dream.router (Ogit.Handlers.routes config)) in
32
test (fun target ->
33
Dream.request ~target "" |> request |> Dream.body |> Lwt_main.run))
34
35
let count_occurrences needle haystack =
36
let nl = String.length needle and hl = String.length haystack in
37
let rec go index count =
38
if index + nl > hl then count
39
else if String.sub haystack index nl = needle then go (index + 1) (count + 1)
40
else go (index + 1) count
41
in
42
go 0 0
43
44
let commit_rows body = count_occurrences "commit-title" body
45
let has_link_to_page n body = count_occurrences (Printf.sprintf "page=%d" n) body > 0
46
47
(* 25 commits at page size 10: the first page holds exactly the page size and
48
links onward. *)
49
let test_first_page () =
50
with_repository_of ~commits:25 ~page_size:10 (fun body ->
51
let page = body "/project/commits/" in
52
Alcotest.(check int) "rows" 10 (commit_rows page);
53
Alcotest.(check bool) "next link" true (has_link_to_page 2 page))
54
55
(* The remaining 15 commits fit within page size + 10, so the second page
56
absorbs them all rather than leaving a five-commit final page. *)
57
let test_orphan_absorption () =
58
with_repository_of ~commits:25 ~page_size:10 (fun body ->
59
let page = body "/project/commits/?page=2" in
60
Alcotest.(check int) "absorbed rows" 15 (commit_rows page);
61
Alcotest.(check bool) "no next link" false (has_link_to_page 3 page))
62
63
(* 35 commits at page size 10: 25 remain after page one, beyond the
64
absorption window of page size + 10, so page two stays at the page size and
65
links onward. *)
66
let test_beyond_absorption_window () =
67
with_repository_of ~commits:35 ~page_size:10 (fun body ->
68
let page = body "/project/commits/?page=2" in
69
Alcotest.(check int) "rows" 10 (commit_rows page);
70
Alcotest.(check bool) "next link" true (has_link_to_page 3 page))
71
72
(* Exactly page size + 10 remaining is the widest page absorption produces:
73
with 30 commits, the 20 left after page one all land on page two. *)
74
let test_absorption_window_boundary () =
75
with_repository_of ~commits:30 ~page_size:10 (fun body ->
76
let page = body "/project/commits/?page=2" in
77
Alcotest.(check int) "rows" 20 (commit_rows page);
78
Alcotest.(check bool) "no next link" false (has_link_to_page 3 page))
79
80
(* Fewer commits than one page: a single page and no pagination links. *)
81
let test_single_page () =
82
with_repository_of ~commits:4 ~page_size:10 (fun body ->
83
let page = body "/project/commits/" in
84
Alcotest.(check int) "rows" 4 (commit_rows page);
85
Alcotest.(check bool) "no next link" false (has_link_to_page 2 page))
86
87
(* A page number beyond the history renders an empty list, not an error. *)
88
let test_page_past_end () =
89
with_repository_of ~commits:4 ~page_size:10 (fun body ->
90
let page = body "/project/commits/?page=9" in
91
Alcotest.(check int) "no rows" 0 (commit_rows page))
92
93
let suite =
94
( "pagination",
95
[
96
Alcotest.test_case "first page" `Slow test_first_page;
97
Alcotest.test_case "orphan absorption" `Slow test_orphan_absorption;
98
Alcotest.test_case "beyond absorption window" `Slow
99
test_beyond_absorption_window;
100
Alcotest.test_case "absorption window boundary" `Slow
101
test_absorption_window_boundary;
102
Alcotest.test_case "single page" `Slow test_single_page;
103
Alcotest.test_case "page past end" `Slow test_page_past_end;
104
] )
105