diff options
| -rw-r--r-- | clients/bokftui.c | 47 | ||||
| -rw-r--r-- | clients/tui.c | 116 | ||||
| -rw-r--r-- | clients/tui.h | 15 | ||||
| -rw-r--r-- | docs/PROTOCOL.md | 6 | ||||
| -rw-r--r-- | docs/STATE.md | 4 | ||||
| -rw-r--r-- | docs/TUI-GUIDELINES.md | 3 | ||||
| -rwxr-xr-x | scripts/tui-golden.py | 42 | ||||
| -rw-r--r-- | tests/test_tui.c | 43 |
8 files changed, 222 insertions, 54 deletions
diff --git a/clients/bokftui.c b/clients/bokftui.c index 05a845c..fef9100 100644 --- a/clients/bokftui.c +++ b/clients/bokftui.c @@ -29,6 +29,7 @@ #define MK_HEAD "\001" #define MK_DIM "\002" #define MK_RULE "\003" +#define MK_STICKY "\004" /* Menu item prefix for a non-selectable section header. */ #define MK_SECTION MK_HEAD /* ^C quits the application; Esc is navigation only */ @@ -993,6 +994,10 @@ static int voucher_detail(struct app *a, int64_t id, int64_t *out_new) series ? series : "", (long long)number, date ? date : "", desc ? desc : ""); buf_append(&text, line, strlen(line)); + int hdr = snprintf( + line, sizeof line, MK_HEAD " %-6s %-34s D %12s K %12s\n", + "Konto", "Benämning", "Debet", "Kredit"); + buf_append(&text, line, (size_t)hdr); size_t nrows = jarr_size(resp, "result.rows"); for (size_t i = 0; i < nrows; i++) { char path[64]; @@ -1020,7 +1025,13 @@ static int voucher_detail(struct app *a, int64_t id, int64_t *out_new) } size_t natts = jarr_size(resp, "result.attachments"); if (natts) { - buf_append(&text, "\nUnderlag:\n", 11); + buf_append(&text, "\n", 1); + line[0] = MK_RULE[0]; + for (int i = 1; i <= 98; i++) + line[i] = '-'; + line[99] = '\n'; + buf_append(&text, line, 100); + buf_append(&text, MK_HEAD "Underlag:\n", 11); for (size_t i = 0; i < natts; i++) { char path[64]; snprintf(path, sizeof path, "result.attachments.%zu.filename", @@ -1833,8 +1844,9 @@ static void accounts_report(struct app *a) buf_init(&text); char line[2048]; int hdr = snprintf(line, sizeof line, - "%-6s %-*s %-12s %-5s %-6s %-8s\n", "Konto", - name_w, "Namn", "Typ", "Aktiv", "SRU", "Moms"); + MK_STICKY "%-6s %-*s %-12s %-5s %-6s %-8s\n", + "Konto", name_w, "Namn", "Typ", "Aktiv", "SRU", + "Moms"); buf_append(&text, line, (size_t)hdr); int sep_w = 6 + 1 + name_w + 1 + 12 + 1 + 5 + 1 + 6 + 1 + 8; for (int i = 0; i < sep_w && i < (int)sizeof line - 1; i++) @@ -1976,10 +1988,11 @@ struct bs_sum { int64_t ib, per, ub; }; -static void bs_col_head(struct buf *t) +static void bs_col_head(struct buf *t, int sticky) { - buf_line(t, " %5s %-48s %14s %14s %14s %14s\n", "", "", "Ing balans", - "Ing saldo", "Period", "Utg balans"); + buf_line(t, "%s %5s %-48s %14s %14s %14s %14s\n", + sticky ? MK_STICKY MK_HEAD : "", "", "", "Ing balans", "Ing saldo", + "Period", "Utg balans"); buf_rule(t, BS_RULE_W); } @@ -2065,7 +2078,7 @@ static void fmt_balans(struct buf *t, const struct app *a, yyjson_val *res) struct bs_sum anl = { 0, 0, 0 }, oms = { 0, 0, 0 }; struct bs_sum eget = { 0, 0, 0 }, skuld = { 0, 0, 0 }; buf_line(t, MK_HEAD "TILLGÅNGAR\n"); - bs_col_head(t); + bs_col_head(t, 1); buf_line(t, "\n" MK_HEAD "Anläggningstillgångar\n"); bs_rows(t, res, 1000, 1399, &anl); bs_sum_line(t, "Summa anläggningstillgångar", &anl); @@ -2087,7 +2100,7 @@ static void fmt_balans(struct buf *t, const struct app *a, yyjson_val *res) bs_sum_line(t, "Summa tillgångar", &as); buf_line(t, "\n" MK_HEAD "EGET KAPITAL OCH SKULDER\n"); - bs_col_head(t); + bs_col_head(t, 0); buf_line(t, "\n" MK_HEAD "Eget kapital\n"); bs_group(t, res, "Bundet eget kapital", "Summa bundet eget kapital", 2000, 2090, &eget); @@ -2280,8 +2293,8 @@ static void fmt_resultat(struct buf *t, const struct app *a, yyjson_val *res, tui_pad_hdr(h1, sizeof h1, 15, "Period"); tui_pad_hdr(h2, sizeof h2, 15, "Ackumulerat"); tui_pad_hdr(h3, sizeof h3, 15, "Föreg. per."); - buf_line(t, MK_HEAD " %5s %s %s %s %s\n", "", lbl, h1, h2, - h3); + buf_line(t, MK_STICKY MK_HEAD " %5s %s %s %s %s\n", "", lbl, + h1, h2, h3); buf_rule(t, IS_RULE_W); buf_line(t, "\n"); buf_line(t, MK_HEAD "%s\n", gr->title); @@ -2362,8 +2375,8 @@ static void fmt_saldobalans(struct buf *t, const struct app *a, tui_pad_hdr(h2, sizeof h2, 14, "Debet"); tui_pad_hdr(h3, sizeof h3, 14, "Kredit"); tui_pad_hdr(h4, sizeof h4, 14, "Utgående"); - buf_line(t, MK_HEAD " %5s %s %s %s %s %s\n", "Konto", nmh, h1, h2, h3, - h4); + buf_line(t, MK_STICKY MK_HEAD " %5s %s %s %s %s %s\n", "Konto", nmh, h1, + h2, h3, h4); buf_rule(t, BS_RULE_W); yyjson_val *arr = yyjson_obj_get(res, "accounts"); size_t n = yyjson_arr_size(arr); @@ -2541,8 +2554,8 @@ static void fmt_huvudbok(struct buf *t, const struct app *a, yyjson_val *res) if (lv) buf_line(t, MK_DIM "Senaste ver.: %s%lld\n\n", vstr(lv, "series"), (long long)vint(lv, "number")); - buf_line(t, MK_HEAD "%-8s %-58s %14s %14s %16s\n", "Konto", "", "Debet", - "Kredit", "Saldo"); + buf_line(t, MK_STICKY MK_HEAD "%-8s %-58s %14s %14s %16s\n", "Konto", "", + "Debet", "Kredit", "Saldo"); buf_rule(t, 114); yyjson_val *accs = yyjson_obj_get(res, "accounts"); size_t n = yyjson_arr_size(accs); @@ -2604,8 +2617,8 @@ static void fmt_verifikationslista(struct buf *t, const struct app *a, (long long)vint(lv, "number")); char nmh[64]; tui_pad_label(nmh, sizeof nmh, "Benämning", 48); - buf_line(t, MK_HEAD "%-8s %-11s %s %14s %14s\n", "Ver.", "Datum/Konto", - nmh, "Debet", "Kredit"); + buf_line(t, MK_STICKY MK_HEAD "%-8s %-11s %s %14s %14s\n", "Ver.", + "Datum/Konto", nmh, "Debet", "Kredit"); buf_rule(t, 98); yyjson_val *vs = yyjson_obj_get(res, "vouchers"); size_t n = yyjson_arr_size(vs); @@ -5385,7 +5398,7 @@ static void strip_markup(const char *in, struct buf *out) for (const char *p = in; *p;) { const char *nl = strchr(p, '\n'); size_t len = nl ? (size_t)(nl - p) : strlen(p); - if (len && p[0] >= TUI_MARK_HEADING && p[0] <= TUI_MARK_RULE) { + if (len && p[0] >= TUI_MARK_HEADING && p[0] <= TUI_MARK_STICKY) { p++; len--; } diff --git a/clients/tui.c b/clients/tui.c index 06740d3..031fad0 100644 --- a/clients/tui.c +++ b/clients/tui.c @@ -126,6 +126,8 @@ const char *tui_markup(const char *line, int *style) { const char *p = line ? line : ""; int st = -1; + if (*p == TUI_MARK_STICKY) + p++; if (*p == TUI_MARK_HEADING) { st = TUI_HEADING; p++; @@ -141,6 +143,20 @@ const char *tui_markup(const char *line, int *style) return p; } +int tui_sticky_split(char **lines, int n, char **head, int *nhead) +{ + int h = 0, b = 0; + for (int i = 0; i < n; i++) { + if (lines[i][0] == TUI_MARK_STICKY) + head[h++] = lines[i]; + else + lines[b++] = lines[i]; + } + if (nhead) + *nhead = h; + return b; +} + void tui_keys_setup(void) { /* ^Enter has no control code; enable xterm modifyOtherKeys and bind the @@ -1210,6 +1226,28 @@ int tui_pager(const char *title, const char *text, const char *action_hint) action_hint ? TUI_PAGER_SAVE : 0, NULL, NULL); } +static void pager_line(int row, const char *line) +{ + move(row, 2); + clrtoeol(); + if (!line) + return; + int st = -1; + const char *s = tui_markup(line, &st); + if (st == TUI_RULE) { + attrset(tui_style_attrs(TUI_RULE, g_colours, 0)); + for (int x = 2; x < COLS - 1; x++) + mvaddch(row, x, ACS_HLINE); + attrset(A_NORMAL); + } else if (st >= 0) { + attrset(tui_style_attrs((enum tui_style)st, g_colours, 0)); + mvaddnstr(row, 2, s, COLS - 4); + attrset(A_NORMAL); + } else { + mvaddnstr(row, 2, s, COLS - 4); + } +} + int tui_pager_hook(const char *title, const char *text, const char *extra_hint, unsigned flags, int (*key)(void *ud, int ch), void *ud) @@ -1232,29 +1270,24 @@ int tui_pager_hook(const char *title, const char *text, break; p = nl + 1; } + char **head = xcalloc(nlines ? nlines : 1, sizeof(char *)); + int nhead = 0; + int nb = tui_sticky_split(lines, n, head, &nhead); int top = 0; int view = LINES - 4; + int vbody = view - nhead; + if (vbody < 1) + vbody = 1; for (;;) { tui_frame(title); - for (int i = 0; i < view; i++) { - move(2 + i, 2); - clrtoeol(); - if (top + i >= n) - continue; - int st = -1; - const char *s = tui_markup(lines[top + i], &st); - if (st == TUI_RULE) { - attrset(tui_style_attrs(TUI_RULE, g_colours, 0)); - for (int x = 2; x < COLS - 1; x++) - mvaddch(2 + i, x, ACS_HLINE); - attrset(A_NORMAL); - } else if (st >= 0) { - attrset(tui_style_attrs((enum tui_style)st, g_colours, 0)); - mvaddnstr(2 + i, 2, s, COLS - 4); - attrset(A_NORMAL); - } else { - mvaddnstr(2 + i, 2, s, COLS - 4); - } + int row = 0; + for (int i = 0; i < nhead && row < view; i++, row++) + pager_line(2 + row, head[i]); + for (int i = 0; i < vbody && row < view; i++, row++) { + if (top + i < nb) + pager_line(2 + row, lines[top + i]); + else + pager_line(2 + row, NULL); } char hint[512]; if (extra_hint && *extra_hint) @@ -1290,23 +1323,24 @@ int tui_pager_hook(const char *title, const char *text, if (h == TUI_HOOK_STAY) continue; } - if (ch == KEY_DOWN && top + view < n) + if (ch == KEY_DOWN && top + vbody < nb) top++; else if (ch == KEY_UP && top > 0) top--; else if (ch == KEY_NPAGE) - top += view; + top += vbody; else if (ch == KEY_PPAGE) - top -= view; + top -= vbody; else if (ch == KEY_HOME) top = 0; else if (ch == KEY_END) - top = n > view ? n - view : 0; - if (top + view > n) - top = n > view ? n - view : 0; + top = nb > vbody ? nb - vbody : 0; + if (top + vbody > nb) + top = nb > vbody ? nb - vbody : 0; if (top < 0) top = 0; } + free(head); free(copy); free(lines); return ret; @@ -1410,6 +1444,33 @@ int tui_form_act_first(int nf, const struct tui_form_action *acts, int na) return -1; } +int tui_form_act_page(int focus, int nf, const struct tui_form_action *acts, + int na, int dir) +{ + if (nf < 0) + nf = 0; + if (na < 0) + na = 0; + int n = nf + na; + if (n <= 0) + return -1; + int pos = focus >= 0 && focus < n ? focus : 0; + for (int i = 0; i < TUI_FORM_PAGE; i++) { + int next = -1; + for (int p = pos + (dir >= 0 ? 1 : -1); p >= 0 && p < n; + p += (dir >= 0 ? 1 : -1)) { + if (p < nf || act_selectable(acts, p - nf, na)) { + next = p; + break; + } + } + if (next < 0) + break; + pos = next; + } + return pos; +} + int tui_form_act_key(int sel, int nf, const struct tui_form_action *acts, int na, int ch) { @@ -1429,10 +1490,7 @@ int tui_form_act_key(int sel, int nf, const struct tui_form_action *acts, } if (ch == KEY_NPAGE || ch == KEY_PPAGE) { int dir = ch == KEY_NPAGE ? 1 : -1; - int pos = sel; - for (int i = 0; i < TUI_FORM_PAGE; i++) - pos = tui_form_act_step(pos, nf, acts, na, dir); - return pos; + return tui_form_act_page(sel, nf, acts, na, dir); } return tui_form_next(sel, nf, ch); } diff --git a/clients/tui.h b/clients/tui.h index fcab5b7..6df27fb 100644 --- a/clients/tui.h +++ b/clients/tui.h @@ -37,13 +37,20 @@ void tui_style(enum tui_style s); void tui_style_reset(void); /* Pager line markup. A leading marker selects a style; \x03 is a rule and - is drawn full width as ACS_HLINE. tui_markup returns the text after the - marker and sets *style to the matching style, or -1 for plain text. */ + is drawn full width as ACS_HLINE; \x04 pins the line as a column header + above the scrolling body. tui_markup returns the text after the marker(s) + and sets *style to the matching style, or -1 for plain text. */ #define TUI_MARK_HEADING 0x01 #define TUI_MARK_DIM 0x02 #define TUI_MARK_RULE 0x03 +#define TUI_MARK_STICKY 0x04 const char *tui_markup(const char *line, int *style); +/* Moves sticky-marked lines from lines[] to head[] (both in input order), + compacts the remaining body lines in lines[] and returns the body count. + head must hold at least n pointers and must not alias lines. Pure. */ +int tui_sticky_split(char **lines, int n, char **head, int *nhead); + /* Key hooks: widgets call the screen's hook for keys they do not handle. */ #define TUI_HOOK_NONE 0 /* not handled: process the key normally */ #define TUI_HOOK_STAY 1 /* handled: redraw and keep the widget up */ @@ -213,6 +220,10 @@ int tui_form_focus_store(int *focus, int sel, int ret); are skipped and the ring wraps. dir +1 = Tab/Down, -1 = Shift-Tab/Up. */ int tui_form_act_step(int focus, int nf, const struct tui_form_action *acts, int na, int dir); +/* Like tui_form_act_step, but pages (up to TUI_FORM_PAGE rows) and stops at + the first/last selectable row instead of wrapping. dir +1 = PgDn. */ +int tui_form_act_page(int focus, int nf, const struct tui_form_action *acts, + int na, int dir); /* Same ring for one key, plus Home/End/PgUp/PgDn and the form return keys (F5/^Enter/F9/Esc/q). Returns the new focus, a TUI_FORM_* code or TUI_NAV_NONE. */ diff --git a/docs/PROTOCOL.md b/docs/PROTOCOL.md index 8405ca5..6f26463 100644 --- a/docs/PROTOCOL.md +++ b/docs/PROTOCOL.md @@ -503,8 +503,9 @@ commands. Implemented screens (0.1.0-dev): - **Inloggning** — server, user, password; org picker when several exist. - **Dashboard** — status line with org, fiscal year, role and user. -- **Verifikat** — list and detail view (rows, attachments, hash, link to - corrected voucher); `c` posts an ändringsverifikat. +- **Verifikat** — list and detail view (rows with column headers, an + underlag section separated by a rule, hash, link to corrected voucher); `c` + posts an ändringsverifikat. - **Nytt verifikat** — row editor with live balance display, F5 dry-run validation and F9 posting; one `client_ref` per form makes retries safe. F4 applies a konteringsmall (prompts for template and `x`). @@ -525,6 +526,7 @@ commands. Implemented screens (0.1.0-dev): inkomstdeklaration (INK2/SRU), årsredovisning (K2 text draft) and kontolista (all accounts with type, moms treatment, SRU and status). `s` saves the SRU files, eSKD XML and the årsredovisning respectively. + Report tables keep their column-header row pinned while the body scrolls. The draft asks for the board's proposed dividend, kept per fiscal year with `fiscal_year.update`. - **Bokslut** — periodiseringsfond and tax rate fields; F5 shows the posting diff --git a/docs/STATE.md b/docs/STATE.md index de87186..526c1a6 100644 --- a/docs/STATE.md +++ b/docs/STATE.md @@ -91,6 +91,10 @@ check. saldo/Period/Utg balans and Beräknat resultat, Momsrapport ruta för ruta). The TUI never shows report JSON. Amounts are Swedish formatted (`1 234,56`); moms rutas are whole kronor truncated like Kapitas. + Report tables mark their column-header row with `\x04` (`TUI_MARK_STICKY`) + so the pager pins it above the scrolling body; the verifikat detail shows + column headers and separates the underlag section with a rule. PgUp/PgDn + page without wrapping and stop at the first/last row. 16. **Moms rules (schema v3)**: default seed covers 05 over 3000-3019, 3100-3199, 3300-3399, reverse-charge sales (32xx) in 41, EU purchases in 20/21, reverse-charge output VAT 2614/2624/2634 in 30/31/32, and box 48 diff --git a/docs/TUI-GUIDELINES.md b/docs/TUI-GUIDELINES.md index 3bd3c65..09605e9 100644 --- a/docs/TUI-GUIDELINES.md +++ b/docs/TUI-GUIDELINES.md @@ -24,7 +24,7 @@ there. | `Ctrl+A` | Close or reopen the highlighted räkenskapsår (Räkenskapsår screen; asks for confirmation) | | `1`–`9` | In lists: jump to that row. In menus: activate that item | | `g` | Live goto: "Gå till rad/nummer:" updates the selection as you type digits (backspace steps back); Enter closes the prompt without opening anything | -| arrows, PgUp/PgDn, Home/End | Move/scroll; selection always stays visible | +| arrows, PgUp/PgDn, Home/End | Move/scroll; selection always stays visible. Pages stop at the first/last row, they never wrap | | `e` | Edit the shown object (IB, where applicable) | | `a` | Add/upload (Underlag) | | `c` | Correct (voucher detail) | @@ -193,6 +193,7 @@ formatters, interpreted by `tui_markup`): | `\x01` | line is a heading (`TUI_HEADING`) | | `\x02` | line is dimmed (`TUI_DIM`) | | `\x03` | full-width `ACS_HLINE` rule (`TUI_RULE`); the rest of the line is ignored | +| `\x04` | line is pinned as a column header: the pager keeps it visible above the scrolling body (combine with another marker, e.g. `\x04\x01`) | Report formatters mark headings, sums/derived lines and rules before the text reaches `tui_pager`; the markers never change column widths. Anything diff --git a/scripts/tui-golden.py b/scripts/tui-golden.py index 589b693..33f1d7c 100755 --- a/scripts/tui-golden.py +++ b/scripts/tui-golden.py @@ -88,6 +88,32 @@ SCENARIOS = [ "10. 20 range 4510-4529", ], }, + { + "name": "voucher-detail", + "screen": "vouchers", + "steps": [ + { + "keys": ["enter"], + "expect": ["Benämning", "Debet", "Kredit", "Underlag", + "kvitto.txt"], + }, + ], + }, + { + "name": "kontolista-sticky", + "screen": "reports", + "expect": ["Rapporter"], + "steps": [ + { + "keys": ["7"], + "expect": ["Konto", "Namn"], + }, + { + "keys": ["end"], + "expect": ["Konto", "Namn", "konton, varav"], + }, + ], + }, ] # -------------------------------------------------------------------------- @@ -558,6 +584,22 @@ def setup_rig(repo, tmp, env): "--org", str(org_id), "fiscal_year.get", json.dumps({"id": fy_id})], env) fy = json.loads(out)["result"] + out = run_checked([str(bind / "bokfctl"), "--socket", str(sock), + "--user", "admin", "--password", env["BOKFD_PASSWORD"], + "--org", str(org_id), "voucher.post", + json.dumps({ + "date": fy.get("start_date", "2026-01-01"), + "description": "Golden verifikat", + "rows": [{"account": "1930", "debit_ore": 12300}, + {"account": "3001", "credit_ore": 12300}], + })], env) + vid = json.loads(out)["result"]["id"] + run_checked([str(bind / "bokfctl"), "--socket", str(sock), + "--user", "admin", "--password", env["BOKFD_PASSWORD"], + "--org", str(org_id), "attachment.put", + json.dumps({"filename": "kvitto.txt", + "content_base64": "a3ZpdHRv", + "voucher_id": vid})], env) return daemon, sock, org_id, fy diff --git a/tests/test_tui.c b/tests/test_tui.c index 56880fa..84e610e 100644 --- a/tests/test_tui.c +++ b/tests/test_tui.c @@ -303,11 +303,39 @@ static void test_markup(void) s = tui_markup("a\001b", &st); CHECK(strcmp(s, "a\001b") == 0 && st == -1); s = tui_markup("\004text", &st); - CHECK(strcmp(s, "\004text") == 0 && st == -1); + CHECK(strcmp(s, "text") == 0 && st == -1); + s = tui_markup("\004\001Rubrik", &st); + CHECK(strcmp(s, "Rubrik") == 0 && st == TUI_HEADING); s = tui_markup("\001x", NULL); CHECK(strcmp(s, "x") == 0); } +static void test_sticky(void) +{ + char a[] = "alfa"; + char h1[] = "\004RUBRIK 1"; + char b[] = "beta"; + char h2[] = "\004RUBRIK 2"; + char c[] = "gamma"; + char *lines[] = { a, h1, b, h2, c }; + char *head[5]; + int nh = 0; + int nb = tui_sticky_split(lines, 5, head, &nh); + CHECK(nb == 3 && nh == 2); + CHECK(strcmp(lines[0], "alfa") == 0); + CHECK(strcmp(lines[1], "beta") == 0); + CHECK(strcmp(lines[2], "gamma") == 0); + CHECK(strcmp(head[0], "\004RUBRIK 1") == 0); + CHECK(strcmp(head[1], "\004RUBRIK 2") == 0); + char *one[] = { a }; + nb = tui_sticky_split(one, 1, head, &nh); + CHECK(nb == 1 && nh == 0); + char *none[] = { h1, h2 }; + nb = tui_sticky_split(none, 2, head, &nh); + CHECK(nb == 0 && nh == 2); + CHECK(head[0] == h1 && head[1] == h2); +} + static const unsigned char SECTION_FLAGS[5] = { 0, 1, 1, 0, 1 }; static void test_nav_sections(void) @@ -417,8 +445,16 @@ static void test_form_actions(void) CHECK(tui_form_act_key(8, nf, ACTS, 6, KEY_DOWN) == 0); CHECK(tui_form_act_key(4, nf, ACTS, 6, KEY_HOME) == 0); CHECK(tui_form_act_key(0, nf, ACTS, 6, KEY_END) == 8); - CHECK(tui_form_act_key(0, nf, ACTS, 6, KEY_NPAGE) == 4); - CHECK(tui_form_act_key(8, nf, ACTS, 6, KEY_PPAGE) == 4); + /* paging stops at the ends instead of wrapping */ + CHECK(tui_form_act_key(0, nf, ACTS, 6, KEY_NPAGE) == 8); + CHECK(tui_form_act_key(8, nf, ACTS, 6, KEY_PPAGE) == 0); + CHECK(tui_form_act_key(8, nf, ACTS, 6, KEY_NPAGE) == 8); + CHECK(tui_form_act_key(0, nf, ACTS, 6, KEY_PPAGE) == 0); + CHECK(tui_form_act_page(0, nf, ACTS, 6, 1) == 8); + CHECK(tui_form_act_page(8, nf, ACTS, 6, -1) == 0); + CHECK(tui_form_act_page(4, nf, ACTS, 6, 1) == 8); + CHECK(tui_form_act_page(4, nf, ACTS, 6, -1) == 0); + CHECK(tui_form_act_page(0, 0, NULL, 0, 1) == -1); CHECK(tui_form_act_key(0, nf, ACTS, 6, KEY_F(5)) == TUI_FORM_REFRESH); CHECK(tui_form_act_key(5, nf, ACTS, 6, TUI_KEY_CTRL_ENTER) == TUI_FORM_SUBMIT); @@ -687,6 +723,7 @@ int main(void) test_date_field(); test_styles(); test_markup(); + test_sticky(); test_nav(); test_nav_sections(); test_form_nav(); |
