From 4329ffb98d7eeffe2b3874d2b31cb0857d33d944 Mon Sep 17 00:00:00 2001 From: Anders Betts Date: Sat, 19 Sep 2026 21:58:58 +0200 Subject: tui: shared widget layer (fields, prompts, lists, forms, pager) + tests --- tests/test_tui.c | 221 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 tests/test_tui.c (limited to 'tests/test_tui.c') diff --git a/tests/test_tui.c b/tests/test_tui.c new file mode 100644 index 0000000..e056c47 --- /dev/null +++ b/tests/test_tui.c @@ -0,0 +1,221 @@ +/* Unit tests for the TUI widget layer (clients/tui.c). Only the pure parts + are exercised here; drawing is smoke-tested over a pty. */ +#include +#include + +#include + +#include "tui.h" +#include "util.h" + +static int failures = 0; +static int checks = 0; + +#define CHECK(cond) \ + do { \ + checks++; \ + if (!(cond)) { \ + failures++; \ + fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, \ + #cond); \ + } \ + } while (0) + +static void test_disp_width(void) +{ + CHECK(tui_disp_width("") == 0); + CHECK(tui_disp_width("abc") == 3); + CHECK(tui_disp_width("åäö") == 3); + CHECK(tui_disp_width("aåb") == 3); + CHECK(tui_disp_width_n("åäö", 2) == 1); + CHECK(tui_disp_width_n("åäö", 4) == 2); + CHECK(tui_disp_width_n("abc", 2) == 2); +} + +static void test_formats(void) +{ + char buf[64]; + tui_kr_format(123456, buf, sizeof buf); + CHECK(strcmp(buf, "1 234,56") == 0); + tui_kr_format(-1000000, buf, sizeof buf); + CHECK(strcmp(buf, "-10 000,00") == 0); + tui_kr_format(5, buf, sizeof buf); + CHECK(strcmp(buf, "0,05") == 0); + tui_kr0_format(123456, buf, sizeof buf); + CHECK(strcmp(buf, "1 235") == 0); + tui_kr0_format(-123456, buf, sizeof buf); + CHECK(strcmp(buf, "-1 235") == 0); + tui_amt_col(buf, sizeof buf, 12, 123456); + CHECK(strcmp(buf, " 1 234,56") == 0); + snprintf(buf, sizeof buf, "åäö"); + tui_pad_field(buf, sizeof buf, 2); + CHECK(strcmp(buf, "åä") == 0); +} + +static void test_parse_kr(void) +{ + int64_t v = -1; + CHECK(tui_parse_kr("", &v) == 0 && v == 0); + CHECK(tui_parse_kr("0", &v) == 0 && v == 0); + CHECK(tui_parse_kr("12", &v) == 0 && v == 1200); + CHECK(tui_parse_kr("1 234,56", &v) == 0 && v == 123456); + CHECK(tui_parse_kr("12,3", &v) == 0 && v == 1230); + CHECK(tui_parse_kr("12.30", &v) == 0 && v == 1230); + CHECK(tui_parse_kr("1.234", &v) != 0); + CHECK(tui_parse_kr("1,23,4", &v) != 0); + CHECK(tui_parse_kr("-5", &v) != 0); + CHECK(tui_parse_kr("12a", &v) != 0); +} + +static void test_ledit(void) +{ + char buf[32]; + struct tui_ledit e; + + snprintf(buf, sizeof buf, "abc"); + tui_le_init(&e, buf, sizeof buf); + CHECK(e.pos == 3 && e.len == 3); + CHECK(tui_le_key(&e, KEY_LEFT) && e.pos == 2); + CHECK(tui_le_key(&e, 'X')); + CHECK(strcmp(buf, "abXc") == 0 && e.pos == 3); + CHECK(tui_le_key(&e, KEY_BACKSPACE)); + CHECK(strcmp(buf, "abc") == 0 && e.pos == 2); + CHECK(tui_le_key(&e, KEY_DC)); + CHECK(strcmp(buf, "ab") == 0 && e.pos == 2); + CHECK(tui_le_key(&e, KEY_HOME) && e.pos == 0); + CHECK(tui_le_key(&e, 21)); /* ^U */ + CHECK(strcmp(buf, "") == 0 && e.len == 0); + + /* UTF-8: the caret moves by code point, not by byte */ + snprintf(buf, sizeof buf, "åä"); + tui_le_init(&e, buf, sizeof buf); + CHECK(e.pos == 4); + CHECK(tui_le_key(&e, KEY_LEFT) && e.pos == 2); + CHECK(tui_le_key(&e, KEY_LEFT) && e.pos == 0); + CHECK(tui_le_key(&e, KEY_RIGHT) && e.pos == 2); + CHECK(tui_le_key(&e, KEY_BACKSPACE)); + CHECK(strcmp(buf, "ä") == 0); + + /* capacity: never overflow */ + snprintf(buf, sizeof buf, "12345"); + tui_le_init(&e, buf, 4); + CHECK(tui_le_key(&e, '9') == 1 || 1); + CHECK(strlen(buf) < 4 && e.len < 4); + CHECK(tui_le_key(&e, KEY_UP) == 0); /* not an editing key */ +} + +static void date_feed(char *buf, size_t cap, const char *digits) +{ + size_t pos = (size_t)-1; + int fresh = 1; + for (const char *p = digits; *p; p++) + tui_date_field_edit(buf, cap, &pos, *p, &fresh); +} + +static void test_date_field(void) +{ + char buf[16] = ""; + size_t pos = (size_t)-1; + int fresh = 1; + + date_feed(buf, sizeof buf, "20260604"); + CHECK(strcmp(buf, "2026-06-04") == 0); + fresh = 0; + CHECK(tui_date_field_edit(buf, sizeof buf, &pos, KEY_BACKSPACE, &fresh)); + CHECK(strcmp(buf, "2026-06-0") == 0); + CHECK(tui_date_field_edit(buf, sizeof buf, &pos, 21, &fresh)); /* ^U */ + CHECK(strcmp(buf, "") == 0); + CHECK(tui_date_field_edit(buf, sizeof buf, &pos, 'x', &fresh) == 0); + + /* fresh replaces a prefilled value on the first digit */ + snprintf(buf, sizeof buf, "2025-01-01"); + pos = (size_t)-1; + fresh = 1; + tui_date_field_edit(buf, sizeof buf, &pos, '9', &fresh); + CHECK(strcmp(buf, "9") == 0); + /* full field overwrites at the caret */ + date_feed(buf, sizeof buf, "20260604"); + pos = (size_t)-1; + fresh = 0; + tui_date_field_edit(buf, sizeof buf, &pos, '9', &fresh); + CHECK(strcmp(buf, "2026-06-09") == 0); +} + +static void nav_init(struct tui_list_nav *v, int n) +{ + memset(v, 0, sizeof *v); + v->n = n; + v->view = 10; +} + +static void test_nav(void) +{ + struct tui_list_nav v; + + nav_init(&v, 0); + CHECK(tui_nav_key(&v, KEY_DOWN, 0, 0, 0, 0) == TUI_NAV_NONE); + + nav_init(&v, 5); + CHECK(tui_nav_key(&v, KEY_UP, 0, 0, 0, 0) == TUI_NAV_NONE && v.sel == 0); + CHECK(tui_nav_key(&v, KEY_DOWN, 0, 0, 0, 0) == TUI_NAV_NONE && v.sel == 1); + CHECK(tui_nav_key(&v, KEY_END, 0, 0, 0, 0) == TUI_NAV_NONE && v.sel == 4); + CHECK(tui_nav_key(&v, KEY_DOWN, 0, 0, 0, 0) == TUI_NAV_NONE && v.sel == 4); + CHECK(tui_nav_key(&v, KEY_HOME, 0, 0, 0, 0) == TUI_NAV_NONE && v.sel == 0); + CHECK(tui_nav_key(&v, KEY_NPAGE, 0, 0, 0, 0) == TUI_NAV_NONE && v.sel == 4); + CHECK(tui_nav_key(&v, KEY_PPAGE, 0, 0, 0, 0) == TUI_NAV_NONE && v.sel == 0); + + /* digits jump in lists, activate in menus */ + CHECK(tui_nav_key(&v, '3', 0, 0, 0, 0) == TUI_NAV_NONE && v.sel == 2); + CHECK(tui_nav_key(&v, '3', 0, 0, 0, 1) == 2); + CHECK(tui_nav_key(&v, '9', 0, 0, 0, 0) == TUI_NAV_NONE); /* out of range */ + + CHECK(tui_nav_key(&v, '\n', 0, 0, 0, 0) == v.sel); + CHECK(tui_nav_key(&v, KEY_F(5), 0, 0, 0, 0) == -2); + CHECK(tui_nav_key(&v, TUI_KEY_CTRL_N, 1, 0, 0, 0) == -4); + CHECK(tui_nav_key(&v, TUI_KEY_CTRL_N, 0, 0, 0, 0) == TUI_NAV_NONE); + CHECK(tui_nav_key(&v, 'd', 0, 1, 0, 0) == -6); + CHECK(tui_nav_key(&v, 'd', 0, 0, 0, 0) == TUI_NAV_NONE); + CHECK(tui_nav_key(&v, 1, 0, 0, 1, 0) == -7); /* ^A */ + CHECK(tui_nav_key(&v, 'q', 0, 0, 0, 0) == -1); + CHECK(tui_nav_key(&v, 27, 0, 0, 0, 0) == -1); + + /* g goto takes multi-digit input and Enter only closes the prompt */ + nav_init(&v, 20); + CHECK(tui_nav_key(&v, 'g', 0, 0, 0, 0) == TUI_NAV_NONE && v.goto_active); + CHECK(tui_nav_key(&v, '1', 0, 0, 0, 0) == TUI_NAV_NONE && v.sel == 0); + CHECK(tui_nav_key(&v, '2', 0, 0, 0, 0) == TUI_NAV_NONE && v.sel == 11); + CHECK(tui_nav_key(&v, KEY_BACKSPACE, 0, 0, 0, 0) == TUI_NAV_NONE && + v.sel == 0); + CHECK(tui_nav_key(&v, '\n', 0, 0, 0, 0) == TUI_NAV_NONE && !v.goto_active); + /* the view follows the selection */ + nav_init(&v, 100); + v.view = 10; + tui_nav_key(&v, KEY_END, 0, 0, 0, 0); + CHECK(v.top == 90); +} + +static void test_form_nav(void) +{ + CHECK(tui_form_next(0, 3, KEY_UP) == TUI_NAV_NONE); + CHECK(tui_form_next(0, 3, KEY_DOWN) == 1); + CHECK(tui_form_next(2, 3, KEY_DOWN) == TUI_NAV_NONE); + CHECK(tui_form_next(1, 3, KEY_F(5)) == TUI_FORM_REFRESH); + CHECK(tui_form_next(1, 3, TUI_KEY_CTRL_ENTER) == TUI_FORM_SUBMIT); + CHECK(tui_form_next(1, 3, KEY_F(9)) == TUI_FORM_SUBMIT); + CHECK(tui_form_next(1, 3, 27) == TUI_FORM_BACK); + CHECK(tui_form_next(1, 3, 'q') == TUI_FORM_BACK); + CHECK(tui_form_next(1, 3, 'x') == TUI_NAV_NONE); +} + +int main(void) +{ + test_disp_width(); + test_formats(); + test_parse_kr(); + test_ledit(); + test_date_field(); + test_nav(); + test_form_nav(); + printf("test_tui: %d checks, %d failures\n", checks, failures); + return failures ? 1 : 0; +} -- cgit v1.3