View raw

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