aboutsummaryrefslogtreecommitdiff
path: root/tests/test_tui.c
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test_tui.c')
-rw-r--r--tests/test_tui.c221
1 files changed, 221 insertions, 0 deletions
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 <stdio.h>
+#include <string.h>
+
+#include <ncursesw/ncurses.h>
+
+#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;
+}