Terminal styling and layout widgets for OCaml (tables, trees, panels, colors)
1(*---------------------------------------------------------------------------
2 Copyright (c) 2025 Thomas Gazagnaire. All rights reserved.
3 SPDX-License-Identifier: MIT
4 ---------------------------------------------------------------------------*)
5
6open Crowbar
7open Tty
8
9(* Width is always non-negative *)
10let test_width_non_negative s =
11 let w = Width.string_width s in
12 check (w >= 0)
13
14(* Span width matches underlying width *)
15let test_span_width_consistency s =
16 let span = Span.text s in
17 let span_w = Span.width span in
18 let direct_w = Width.string_width s in
19 check (span_w = direct_w)
20
21(* Span concatenation width is sum of widths *)
22let test_span_concat_width s1 s2 =
23 let span1 = Span.text s1 in
24 let span2 = Span.text s2 in
25 let combined = Span.(span1 ++ span2) in
26 let w1 = Span.width span1 in
27 let w2 = Span.width span2 in
28 let w_combined = Span.width combined in
29 check (w_combined = w1 + w2)
30
31(* Table rendering doesn't crash *)
32let test_table_render_no_crash headers row =
33 if List.length headers > 0 && List.length row > 0 then
34 let cols = List.map Table.column headers in
35 let rows = [ List.map Span.text row ] in
36 let table = Table.of_rows cols rows in
37 let _ = Table.to_string table in
38 check true
39 else check true
40
41(* Tree rendering doesn't crash *)
42let test_tree_render_no_crash label =
43 let tree = Tree.of_tree (Tree.Node (Span.text label, [])) in
44 let _ = Tree.to_string tree in
45 check true
46
47(* Panel rendering doesn't crash *)
48let test_panel_render_no_crash content =
49 let panel = Panel.v (Span.text content) in
50 let _ = Panel.to_string panel in
51 check true
52
53(* Color hex parsing and equality *)
54let test_color_rgb_roundtrip r g b =
55 let c = Color.rgb r g b in
56 let c2 = Color.rgb r g b in
57 check (Color.equal c c2)
58
59(* Truncate never increases width *)
60let test_truncate_width s width =
61 let width = abs width mod 1000 in
62 let truncated = Width.truncate width s in
63 let truncated_w = Width.string_width truncated in
64 check (truncated_w <= width)
65
66let suite =
67 ( "tty",
68 [
69 test_case "width non-negative" [ bytes ] test_width_non_negative;
70 test_case "span width consistency" [ bytes ] test_span_width_consistency;
71 test_case "span concat width" [ bytes; bytes ] test_span_concat_width;
72 test_case "table render"
73 [ list bytes; list bytes ]
74 test_table_render_no_crash;
75 test_case "tree render" [ bytes ] test_tree_render_no_crash;
76 test_case "panel render" [ bytes ] test_panel_render_no_crash;
77 test_case "color rgb roundtrip" [ int; int; int ] test_color_rgb_roundtrip;
78 test_case "truncate width" [ bytes; int ] test_truncate_width;
79 ] )