LCOV - code coverage report
Current view: top level - tests/unit - test_tui_history_pane.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 100.0 % 156 156
Test Date: 2026-04-20 19:54:22 Functions: 100.0 % 16 16

            Line data    Source code
       1              : /**
       2              :  * @file tests/unit/test_tui_history_pane.c
       3              :  * @brief Unit tests for the history pane view-model (US-11 v2).
       4              :  */
       5              : 
       6              : #include "test_helpers.h"
       7              : #include "tui/history_pane.h"
       8              : 
       9              : #include <string.h>
      10              : 
      11              : /* --- Helpers --- */
      12              : 
      13          111 : static HistoryEntry mk_text(int32_t id, int outgoing, const char *text) {
      14          111 :     HistoryEntry e = {0};
      15          111 :     e.id = id;
      16          111 :     e.out = outgoing;
      17          111 :     if (text) {
      18          111 :         strncpy(e.text, text, HISTORY_TEXT_MAX - 1);
      19          111 :         e.text[HISTORY_TEXT_MAX - 1] = '\0';
      20              :     }
      21          111 :     return e;
      22              : }
      23              : 
      24            2 : static HistoryEntry mk_complex(int32_t id) {
      25            2 :     HistoryEntry e = {0};
      26            2 :     e.id = id;
      27            2 :     e.complex = 1;
      28            2 :     return e;
      29              : }
      30              : 
      31            1 : static HistoryEntry mk_media(int32_t id, MediaKind kind) {
      32            1 :     HistoryEntry e = {0};
      33            1 :     e.id = id;
      34            1 :     e.media = kind;
      35            1 :     return e;
      36              : }
      37              : 
      38            7 : static HistoryPeer mk_peer_self(void) {
      39            7 :     HistoryPeer p = { .kind = HISTORY_PEER_SELF };
      40            7 :     return p;
      41              : }
      42              : 
      43            8 : static void row_text(const Screen *s, int row, char *out, size_t cap) {
      44            8 :     size_t oi = 0;
      45          238 :     for (int c = 0; c < s->cols && oi + 1 < cap; c++) {
      46          230 :         ScreenCell cell = s->back[row * s->cols + c];
      47          230 :         out[oi++] = (cell.cp && cell.cp < 128) ? (char)cell.cp : ' ';
      48              :     }
      49          114 :     while (oi > 0 && out[oi - 1] == ' ') oi--;
      50            8 :     out[oi] = '\0';
      51            8 : }
      52              : 
      53              : /* --- State tests --- */
      54              : 
      55            1 : static void test_init_is_empty_and_unloaded(void) {
      56              :     HistoryPane hp;
      57            1 :     history_pane_init(&hp);
      58            1 :     ASSERT(hp.count == 0, "no entries");
      59            1 :     ASSERT(hp.peer_loaded == 0, "not yet loaded");
      60            1 :     ASSERT(hp.lv.selected == -1, "no selection");
      61              : }
      62              : 
      63            1 : static void test_set_entries_marks_loaded(void) {
      64            1 :     HistoryPane hp; history_pane_init(&hp);
      65            1 :     HistoryPeer p = mk_peer_self();
      66            1 :     HistoryEntry src[2] = { mk_text(10, 1, "hi"), mk_text(9, 0, "hey") };
      67            1 :     history_pane_set_entries(&hp, &p, src, 2);
      68            1 :     ASSERT(hp.peer_loaded == 1, "loaded");
      69            1 :     ASSERT(hp.count == 2, "2 entries");
      70            1 :     ASSERT(hp.peer.kind == HISTORY_PEER_SELF, "peer copied");
      71            1 :     ASSERT(hp.lv.selected == 0, "selection at top");
      72              : }
      73              : 
      74            1 : static void test_set_entries_clamps_overflow(void) {
      75            1 :     HistoryPane hp; history_pane_init(&hp);
      76            1 :     HistoryPeer p = mk_peer_self();
      77              :     /* src must be at least HISTORY_PANE_MAX+1 so the clamp fires without
      78              :      * reading beyond the array bounds. */
      79              :     HistoryEntry src[HISTORY_PANE_MAX + 1];
      80            1 :     memset(src, 0, sizeof(src));
      81          102 :     for (int i = 0; i < HISTORY_PANE_MAX + 1; i++)
      82          101 :         src[i] = mk_text(i + 1, 0, "x");
      83            1 :     history_pane_set_entries(&hp, &p, src, HISTORY_PANE_MAX + 1);
      84            1 :     ASSERT(hp.count == HISTORY_PANE_MAX, "clamped to max");
      85              :     /* Setting count=0 should keep peer_loaded=1 but clear the entries. */
      86            1 :     history_pane_set_entries(&hp, &p, NULL, 0);
      87            1 :     ASSERT(hp.count == 0, "reset to empty");
      88            1 :     ASSERT(hp.peer_loaded == 1, "peer still loaded");
      89            1 :     ASSERT(hp.lv.selected == -1, "no selection on empty");
      90              : }
      91              : 
      92              : /* --- Render tests --- */
      93              : 
      94            1 : static void test_render_unloaded_shows_select_hint(void) {
      95            1 :     Screen s; ASSERT(screen_init(&s, 5, 25) == 0, "init screen");
      96            1 :     HistoryPane hp; history_pane_init(&hp);
      97            1 :     Pane p = { .row = 0, .col = 0, .rows = 5, .cols = 25 };
      98            1 :     hp.lv.rows_visible = p.rows;
      99            1 :     history_pane_render(&hp, &p, &s);
     100            1 :     char buf[64]; row_text(&s, 2, buf, sizeof(buf));
     101            1 :     ASSERT(strstr(buf, "(select a dialog)") != NULL, "hint shown");
     102            1 :     screen_free(&s);
     103              : }
     104              : 
     105            1 : static void test_render_loaded_empty_shows_no_messages(void) {
     106            1 :     Screen s; ASSERT(screen_init(&s, 5, 25) == 0, "init screen");
     107            1 :     HistoryPane hp; history_pane_init(&hp);
     108            1 :     HistoryPeer peer = mk_peer_self();
     109            1 :     history_pane_set_entries(&hp, &peer, NULL, 0);
     110            1 :     Pane p = { .row = 0, .col = 0, .rows = 5, .cols = 25 };
     111            1 :     hp.lv.rows_visible = p.rows;
     112            1 :     history_pane_render(&hp, &p, &s);
     113            1 :     char buf[64]; row_text(&s, 2, buf, sizeof(buf));
     114            1 :     ASSERT(strstr(buf, "(no messages)") != NULL, "empty placeholder");
     115            1 :     screen_free(&s);
     116              : }
     117              : 
     118            1 : static void test_render_rows_show_direction_and_text(void) {
     119            1 :     Screen s; ASSERT(screen_init(&s, 4, 30) == 0, "init screen");
     120            1 :     HistoryPane hp; history_pane_init(&hp);
     121            1 :     HistoryPeer peer = mk_peer_self();
     122              :     HistoryEntry src[3] = {
     123            1 :         mk_text(100, 1, "hello"),   /* outgoing */
     124            1 :         mk_text(99, 0, "hi there"), /* incoming */
     125            1 :         mk_complex(98),
     126              :     };
     127            1 :     history_pane_set_entries(&hp, &peer, src, 3);
     128            1 :     Pane p = { .row = 0, .col = 0, .rows = 4, .cols = 30 };
     129            1 :     hp.lv.rows_visible = p.rows;
     130            1 :     history_pane_render(&hp, &p, &s);
     131            1 :     char row0[64]; row_text(&s, 0, row0, sizeof(row0));
     132            1 :     char row1[64]; row_text(&s, 1, row1, sizeof(row1));
     133            1 :     char row2[64]; row_text(&s, 2, row2, sizeof(row2));
     134            1 :     ASSERT(row0[0] == '>', "outgoing arrow");
     135            1 :     ASSERT(strstr(row0, "[100]") != NULL, "id badge");
     136            1 :     ASSERT(strstr(row0, "hello") != NULL, "text rendered");
     137            1 :     ASSERT(row1[0] == '<', "incoming arrow");
     138            1 :     ASSERT(strstr(row2, "(complex)") != NULL, "complex marker");
     139            1 :     screen_free(&s);
     140              : }
     141              : 
     142            1 : static void test_render_media_without_text_shows_media_marker(void) {
     143            1 :     Screen s; ASSERT(screen_init(&s, 2, 30) == 0, "init screen");
     144            1 :     HistoryPane hp; history_pane_init(&hp);
     145            1 :     HistoryPeer peer = mk_peer_self();
     146            1 :     HistoryEntry src[1] = { mk_media(50, MEDIA_PHOTO) };
     147            1 :     history_pane_set_entries(&hp, &peer, src, 1);
     148            1 :     Pane p = { .row = 0, .col = 0, .rows = 2, .cols = 30 };
     149            1 :     hp.lv.rows_visible = p.rows;
     150            1 :     history_pane_render(&hp, &p, &s);
     151            1 :     char row[64]; row_text(&s, 0, row, sizeof(row));
     152            1 :     ASSERT(strstr(row, "(media)") != NULL, "media marker");
     153            1 :     screen_free(&s);
     154              : }
     155              : 
     156            1 : static void test_render_respects_scroll(void) {
     157            1 :     Screen s; ASSERT(screen_init(&s, 3, 30) == 0, "init screen");
     158            1 :     HistoryPane hp; history_pane_init(&hp);
     159            1 :     HistoryPeer peer = mk_peer_self();
     160              :     HistoryEntry src[6];
     161            7 :     for (int i = 0; i < 6; i++) {
     162            6 :         char text[8]; snprintf(text, sizeof(text), "msg%d", i);
     163            6 :         src[i] = mk_text(1000 + i, 0, text);
     164              :     }
     165            1 :     history_pane_set_entries(&hp, &peer, src, 6);
     166            1 :     Pane p = { .row = 0, .col = 0, .rows = 3, .cols = 30 };
     167            1 :     hp.lv.rows_visible = p.rows;
     168            1 :     list_view_end(&hp.lv);  /* scroll to bottom */
     169            1 :     history_pane_render(&hp, &p, &s);
     170            1 :     char row0[64]; row_text(&s, 0, row0, sizeof(row0));
     171            1 :     char row2[64]; row_text(&s, 2, row2, sizeof(row2));
     172            1 :     ASSERT(strstr(row0, "msg3") != NULL, "row 0 shows msg3 after scroll");
     173            1 :     ASSERT(strstr(row2, "msg5") != NULL, "row 2 shows last message");
     174            1 :     screen_free(&s);
     175              : }
     176              : 
     177            1 : static void test_render_complex_row_is_dimmed(void) {
     178            1 :     Screen s; ASSERT(screen_init(&s, 2, 20) == 0, "init screen");
     179            1 :     HistoryPane hp; history_pane_init(&hp);
     180            1 :     HistoryPeer peer = mk_peer_self();
     181            1 :     HistoryEntry src[1] = { mk_complex(42) };
     182            1 :     history_pane_set_entries(&hp, &peer, src, 1);
     183            1 :     Pane p = { .row = 0, .col = 0, .rows = 2, .cols = 20 };
     184            1 :     hp.lv.rows_visible = p.rows;
     185            1 :     history_pane_render(&hp, &p, &s);
     186            1 :     ASSERT(s.back[0].attrs & SCREEN_ATTR_DIM, "complex row is dimmed");
     187            1 :     screen_free(&s);
     188              : }
     189              : 
     190            1 : static void test_render_null_args_noop(void) {
     191            1 :     Screen s; ASSERT(screen_init(&s, 2, 10) == 0, "init screen");
     192            1 :     Pane p = { .row = 0, .col = 0, .rows = 2, .cols = 10 };
     193            1 :     HistoryPane hp; history_pane_init(&hp);
     194            1 :     history_pane_render(NULL, &p, &s);
     195            1 :     history_pane_render(&hp, NULL, &s);
     196            1 :     history_pane_render(&hp, &p, NULL);
     197            1 :     ASSERT(s.back[0].cp == ' ', "back untouched");
     198            1 :     screen_free(&s);
     199              : }
     200              : 
     201            1 : void test_tui_history_pane_run(void) {
     202            1 :     RUN_TEST(test_init_is_empty_and_unloaded);
     203            1 :     RUN_TEST(test_set_entries_marks_loaded);
     204            1 :     RUN_TEST(test_set_entries_clamps_overflow);
     205            1 :     RUN_TEST(test_render_unloaded_shows_select_hint);
     206            1 :     RUN_TEST(test_render_loaded_empty_shows_no_messages);
     207            1 :     RUN_TEST(test_render_rows_show_direction_and_text);
     208            1 :     RUN_TEST(test_render_media_without_text_shows_media_marker);
     209            1 :     RUN_TEST(test_render_respects_scroll);
     210            1 :     RUN_TEST(test_render_complex_row_is_dimmed);
     211            1 :     RUN_TEST(test_render_null_args_noop);
     212            1 : }
        

Generated by: LCOV version 2.0-1