diff options
| author | Your Name <you@example.com> | 2026-05-18 22:46:53 +0530 |
|---|---|---|
| committer | Your Name <you@example.com> | 2026-05-18 22:46:53 +0530 |
| commit | 910fe47b2ed1284d93ff4d274f5c2341cb0d2d0b (patch) | |
| tree | 2c0e48e153337b22dbe7f9cc531f5e92e91561fb | |
| parent | f2c4b15c72a2fa89caed5d8a11a4ea9832c48be6 (diff) | |
feat: add on-screen keyboard with layout/hit detection tests
| -rw-r--r-- | main/keyboard.c | 162 | ||||
| -rw-r--r-- | main/keyboard.h | 47 | ||||
| -rw-r--r-- | tests/unit/Makefile | 5 | ||||
| -rw-r--r-- | tests/unit/test_keyboard.c | 168 |
4 files changed, 381 insertions, 1 deletions
diff --git a/main/keyboard.c b/main/keyboard.c new file mode 100644 index 0000000..741bd08 --- /dev/null +++ b/main/keyboard.c | |||
| @@ -0,0 +1,162 @@ | |||
| 1 | #include "keyboard.h" | ||
| 2 | #include <string.h> | ||
| 3 | |||
| 4 | static const char *s_alpha_lower[] = { | ||
| 5 | "qwertyuiop", | ||
| 6 | "asdfghjkl", | ||
| 7 | "\001zxcvbnm\b", | ||
| 8 | "\002\003 \004" | ||
| 9 | }; | ||
| 10 | |||
| 11 | static const char *s_alpha_upper[] = { | ||
| 12 | "QWERTYUIOP", | ||
| 13 | "ASDFGHJKL", | ||
| 14 | "\001ZXCVBNM\b", | ||
| 15 | "\002\003 \004" | ||
| 16 | }; | ||
| 17 | |||
| 18 | static const char *s_numsym[] = { | ||
| 19 | "1234567890", | ||
| 20 | "-/:;()$&@\"", | ||
| 21 | "\001.,?!'\\b", | ||
| 22 | "\002\003 \004" | ||
| 23 | }; | ||
| 24 | |||
| 25 | #define CTRL_SHIFT '\001' | ||
| 26 | #define CTRL_LAYER '\002' | ||
| 27 | #define CTRL_SPACE '\003' | ||
| 28 | #define CTRL_DONE '\004' | ||
| 29 | #define CTRL_BS '\b' | ||
| 30 | |||
| 31 | void kb_state_init(kb_state_t *st) { | ||
| 32 | if (!st) return; | ||
| 33 | memset(st, 0, sizeof(*st)); | ||
| 34 | st->layer = KB_ALPHA_LOWER; | ||
| 35 | st->reveal = false; | ||
| 36 | } | ||
| 37 | |||
| 38 | static const char **get_layer(kb_layer_t layer) { | ||
| 39 | switch (layer) { | ||
| 40 | case KB_ALPHA_UPPER: return s_alpha_upper; | ||
| 41 | case KB_NUMSYM: return s_numsym; | ||
| 42 | default: return s_alpha_lower; | ||
| 43 | } | ||
| 44 | } | ||
| 45 | |||
| 46 | static int key_is_ctrl(char c) { | ||
| 47 | return c == CTRL_SHIFT || c == CTRL_LAYER || c == CTRL_SPACE || c == CTRL_DONE || c == CTRL_BS; | ||
| 48 | } | ||
| 49 | |||
| 50 | int kb_get_row_keys(int row, kb_layer_t layer, const char **keys_out) { | ||
| 51 | if (row < 0 || row >= KB_ROW_COUNT) { | ||
| 52 | *keys_out = NULL; | ||
| 53 | return 0; | ||
| 54 | } | ||
| 55 | const char **layer_rows = get_layer(layer); | ||
| 56 | const char *row_str = layer_rows[row]; | ||
| 57 | *keys_out = row_str; | ||
| 58 | return (int)strlen(row_str); | ||
| 59 | } | ||
| 60 | |||
| 61 | static int row_x_offset(int row) { | ||
| 62 | switch (row) { | ||
| 63 | case 0: return 5; | ||
| 64 | case 1: return 14; | ||
| 65 | case 2: return 23; | ||
| 66 | case 3: return 5; | ||
| 67 | default: return 0; | ||
| 68 | } | ||
| 69 | } | ||
| 70 | |||
| 71 | static int key_width_at(int row, int col, int total_keys) { | ||
| 72 | (void)col; | ||
| 73 | if (row == 3) { | ||
| 74 | if (col == 0) return 42; | ||
| 75 | if (col == total_keys - 1) return 50; | ||
| 76 | if (total_keys == 3 && col == 1) return 168; | ||
| 77 | if (total_keys == 4 && col == 1) return 168; | ||
| 78 | if (total_keys == 4 && col == 2) return 42; | ||
| 79 | } | ||
| 80 | return KB_KEY_W; | ||
| 81 | } | ||
| 82 | |||
| 83 | kb_result_t kb_hit_test(int tx, int ty, kb_layer_t layer) { | ||
| 84 | kb_result_t result = {KB_ACTION_NONE, 0}; | ||
| 85 | |||
| 86 | if (ty < KB_START_Y || ty >= KB_START_Y + KB_ROW_COUNT * (KB_KEY_H + KB_KEY_GAP)) { | ||
| 87 | return result; | ||
| 88 | } | ||
| 89 | |||
| 90 | int row = (ty - KB_START_Y) / (KB_KEY_H + KB_KEY_GAP); | ||
| 91 | if (row < 0 || row >= KB_ROW_COUNT) return result; | ||
| 92 | |||
| 93 | const char *row_str; | ||
| 94 | int total_keys = kb_get_row_keys(row, layer, &row_str); | ||
| 95 | if (total_keys == 0) return result; | ||
| 96 | |||
| 97 | int x_off = row_x_offset(row); | ||
| 98 | int cx = x_off; | ||
| 99 | |||
| 100 | for (int col = 0; col < total_keys; col++) { | ||
| 101 | int kw = key_width_at(row, col, total_keys); | ||
| 102 | if (tx >= cx && tx < cx + kw) { | ||
| 103 | char c = row_str[col]; | ||
| 104 | if (c == CTRL_SHIFT) { | ||
| 105 | result.action = KB_ACTION_SHIFT; | ||
| 106 | } else if (c == CTRL_LAYER) { | ||
| 107 | result.action = KB_ACTION_LAYER; | ||
| 108 | } else if (c == CTRL_SPACE) { | ||
| 109 | result.action = KB_ACTION_SPACE; | ||
| 110 | result.ch = ' '; | ||
| 111 | } else if (c == CTRL_DONE) { | ||
| 112 | result.action = KB_ACTION_DONE; | ||
| 113 | } else if (c == CTRL_BS) { | ||
| 114 | result.action = KB_ACTION_BACKSPACE; | ||
| 115 | } else { | ||
| 116 | result.action = KB_ACTION_CHAR; | ||
| 117 | result.ch = c; | ||
| 118 | } | ||
| 119 | return result; | ||
| 120 | } | ||
| 121 | cx += kw + KB_KEY_GAP; | ||
| 122 | } | ||
| 123 | |||
| 124 | return result; | ||
| 125 | } | ||
| 126 | |||
| 127 | void kb_apply(kb_state_t *st, kb_result_t result) { | ||
| 128 | if (!st || result.action == KB_ACTION_NONE) return; | ||
| 129 | |||
| 130 | switch (result.action) { | ||
| 131 | case KB_ACTION_CHAR: | ||
| 132 | if (st->cursor < KB_INPUT_MAX) { | ||
| 133 | st->input[st->cursor++] = result.ch; | ||
| 134 | st->input[st->cursor] = '\0'; | ||
| 135 | } | ||
| 136 | break; | ||
| 137 | case KB_ACTION_BACKSPACE: | ||
| 138 | if (st->cursor > 0) { | ||
| 139 | st->cursor--; | ||
| 140 | st->input[st->cursor] = '\0'; | ||
| 141 | } | ||
| 142 | break; | ||
| 143 | case KB_ACTION_SHIFT: | ||
| 144 | if (st->layer == KB_ALPHA_LOWER) st->layer = KB_ALPHA_UPPER; | ||
| 145 | else if (st->layer == KB_ALPHA_UPPER) st->layer = KB_ALPHA_LOWER; | ||
| 146 | break; | ||
| 147 | case KB_ACTION_LAYER: | ||
| 148 | if (st->layer == KB_NUMSYM) st->layer = KB_ALPHA_LOWER; | ||
| 149 | else st->layer = KB_NUMSYM; | ||
| 150 | break; | ||
| 151 | case KB_ACTION_SPACE: | ||
| 152 | if (st->cursor < KB_INPUT_MAX) { | ||
| 153 | st->input[st->cursor++] = ' '; | ||
| 154 | st->input[st->cursor] = '\0'; | ||
| 155 | } | ||
| 156 | break; | ||
| 157 | case KB_ACTION_DONE: | ||
| 158 | break; | ||
| 159 | default: | ||
| 160 | break; | ||
| 161 | } | ||
| 162 | } | ||
diff --git a/main/keyboard.h b/main/keyboard.h new file mode 100644 index 0000000..d7b3400 --- /dev/null +++ b/main/keyboard.h | |||
| @@ -0,0 +1,47 @@ | |||
| 1 | #ifndef KEYBOARD_H | ||
| 2 | #define KEYBOARD_H | ||
| 3 | |||
| 4 | #include <stdint.h> | ||
| 5 | #include <stdbool.h> | ||
| 6 | |||
| 7 | #define KB_INPUT_MAX 64 | ||
| 8 | #define KB_KEY_W 28 | ||
| 9 | #define KB_KEY_H 36 | ||
| 10 | #define KB_KEY_GAP 2 | ||
| 11 | #define KB_ROW_COUNT 4 | ||
| 12 | #define KB_START_Y 310 | ||
| 13 | |||
| 14 | typedef enum { | ||
| 15 | KB_ALPHA_LOWER, | ||
| 16 | KB_ALPHA_UPPER, | ||
| 17 | KB_NUMSYM | ||
| 18 | } kb_layer_t; | ||
| 19 | |||
| 20 | typedef enum { | ||
| 21 | KB_ACTION_NONE = 0, | ||
| 22 | KB_ACTION_CHAR, | ||
| 23 | KB_ACTION_SHIFT, | ||
| 24 | KB_ACTION_BACKSPACE, | ||
| 25 | KB_ACTION_DONE, | ||
| 26 | KB_ACTION_LAYER, | ||
| 27 | KB_ACTION_SPACE | ||
| 28 | } kb_action_t; | ||
| 29 | |||
| 30 | typedef struct { | ||
| 31 | kb_action_t action; | ||
| 32 | char ch; | ||
| 33 | } kb_result_t; | ||
| 34 | |||
| 35 | typedef struct { | ||
| 36 | char input[KB_INPUT_MAX + 1]; | ||
| 37 | int cursor; | ||
| 38 | bool reveal; | ||
| 39 | kb_layer_t layer; | ||
| 40 | } kb_state_t; | ||
| 41 | |||
| 42 | void kb_state_init(kb_state_t *st); | ||
| 43 | int kb_get_row_keys(int row, kb_layer_t layer, const char **keys_out); | ||
| 44 | kb_result_t kb_hit_test(int tx, int ty, kb_layer_t layer); | ||
| 45 | void kb_apply(kb_state_t *st, kb_result_t result); | ||
| 46 | |||
| 47 | #endif | ||
diff --git a/tests/unit/Makefile b/tests/unit/Makefile index 5bc5eb1..b978891 100644 --- a/tests/unit/Makefile +++ b/tests/unit/Makefile | |||
| @@ -22,7 +22,7 @@ LDFLAGS := -lmbedcrypto -lcjson -lm | |||
| 22 | 22 | ||
| 23 | SECP256K1_OBJ := secp256k1.o precomputed_ecmult.o precomputed_ecmult_gen.o | 23 | SECP256K1_OBJ := secp256k1.o precomputed_ecmult.o precomputed_ecmult_gen.o |
| 24 | 24 | ||
| 25 | TESTS := test_geohash test_identity test_nostr_event test_cashu test_session test_tollgate_client test_lnurl_pay test_lightning_payout test_mcp_handler test_nip04 test_cvm_server test_touch | 25 | TESTS := test_geohash test_identity test_nostr_event test_cashu test_session test_tollgate_client test_lnurl_pay test_lightning_payout test_mcp_handler test_nip04 test_cvm_server test_touch test_keyboard |
| 26 | 26 | ||
| 27 | .PHONY: all test clean $(TESTS) | 27 | .PHONY: all test clean $(TESTS) |
| 28 | 28 | ||
| @@ -84,5 +84,8 @@ test_cvm_server: test_cvm_server.c | |||
| 84 | test_touch: test_touch.c $(REPO_ROOT)/main/touch.c | 84 | test_touch: test_touch.c $(REPO_ROOT)/main/touch.c |
| 85 | $(CC) $(CFLAGS) $< $(REPO_ROOT)/main/touch.c -o $@ $(LDFLAGS) | 85 | $(CC) $(CFLAGS) $< $(REPO_ROOT)/main/touch.c -o $@ $(LDFLAGS) |
| 86 | 86 | ||
| 87 | test_keyboard: test_keyboard.c $(REPO_ROOT)/main/keyboard.c | ||
| 88 | $(CC) $(CFLAGS) $< $(REPO_ROOT)/main/keyboard.c -o $@ $(LDFLAGS) | ||
| 89 | |||
| 87 | clean: | 90 | clean: |
| 88 | rm -f $(TESTS) $(SECP256K1_OBJ) | 91 | rm -f $(TESTS) $(SECP256K1_OBJ) |
diff --git a/tests/unit/test_keyboard.c b/tests/unit/test_keyboard.c new file mode 100644 index 0000000..4cb923d --- /dev/null +++ b/tests/unit/test_keyboard.c | |||
| @@ -0,0 +1,168 @@ | |||
| 1 | #include "test_framework.h" | ||
| 2 | #include "../../main/keyboard.h" | ||
| 3 | #include <string.h> | ||
| 4 | |||
| 5 | int main(void) | ||
| 6 | { | ||
| 7 | printf("=== test_keyboard ===\n"); | ||
| 8 | |||
| 9 | const char *keys; | ||
| 10 | int count; | ||
| 11 | |||
| 12 | count = kb_get_row_keys(0, KB_ALPHA_LOWER, &keys); | ||
| 13 | ASSERT_EQ_INT(10, count, "Row 0 alpha lower has 10 keys"); | ||
| 14 | ASSERT_EQ_INT('q', keys[0], "Row 0 starts with 'q'"); | ||
| 15 | ASSERT_EQ_INT('p', keys[9], "Row 0 ends with 'p'"); | ||
| 16 | |||
| 17 | count = kb_get_row_keys(1, KB_ALPHA_LOWER, &keys); | ||
| 18 | ASSERT_EQ_INT(9, count, "Row 1 alpha lower has 9 keys"); | ||
| 19 | ASSERT_EQ_INT('a', keys[0], "Row 1 starts with 'a'"); | ||
| 20 | |||
| 21 | count = kb_get_row_keys(2, KB_ALPHA_LOWER, &keys); | ||
| 22 | ASSERT(count > 0, "Row 2 alpha lower has keys"); | ||
| 23 | ASSERT_EQ_INT('\001', keys[0], "Row 2 starts with SHIFT control char"); | ||
| 24 | |||
| 25 | count = kb_get_row_keys(0, KB_ALPHA_UPPER, &keys); | ||
| 26 | ASSERT_EQ_INT(10, count, "Row 0 alpha upper has 10 keys"); | ||
| 27 | ASSERT_EQ_INT('Q', keys[0], "Row 0 upper starts with 'Q'"); | ||
| 28 | |||
| 29 | count = kb_get_row_keys(0, KB_NUMSYM, &keys); | ||
| 30 | ASSERT_EQ_INT(10, count, "Row 0 numsym has 10 keys"); | ||
| 31 | ASSERT_EQ_INT('1', keys[0], "Row 0 numsym starts with '1'"); | ||
| 32 | ASSERT_EQ_INT('0', keys[9], "Row 0 numsym ends with '0'"); | ||
| 33 | |||
| 34 | count = kb_get_row_keys(-1, KB_ALPHA_LOWER, &keys); | ||
| 35 | ASSERT_EQ_INT(0, count, "Invalid row -1 returns 0"); | ||
| 36 | |||
| 37 | count = kb_get_row_keys(99, KB_ALPHA_LOWER, &keys); | ||
| 38 | ASSERT_EQ_INT(0, count, "Invalid row 99 returns 0"); | ||
| 39 | |||
| 40 | { | ||
| 41 | kb_result_t r = kb_hit_test(160, 100, KB_ALPHA_LOWER); | ||
| 42 | ASSERT(r.action == KB_ACTION_NONE, "Touch above keyboard = NONE"); | ||
| 43 | |||
| 44 | r = kb_hit_test(160, 500, KB_ALPHA_LOWER); | ||
| 45 | ASSERT(r.action == KB_ACTION_NONE, "Touch below keyboard = NONE"); | ||
| 46 | } | ||
| 47 | |||
| 48 | { | ||
| 49 | int mid_x = 5 + KB_KEY_W / 2; | ||
| 50 | int mid_y = KB_START_Y + KB_KEY_H / 2; | ||
| 51 | kb_result_t r = kb_hit_test(mid_x, mid_y, KB_ALPHA_LOWER); | ||
| 52 | ASSERT(r.action == KB_ACTION_CHAR, "Row 0 first key is a char"); | ||
| 53 | ASSERT_EQ_INT('q', r.ch, "Row 0 first key = 'q'"); | ||
| 54 | } | ||
| 55 | |||
| 56 | { | ||
| 57 | int x = 5 + KB_KEY_W + KB_KEY_GAP + KB_KEY_W / 2; | ||
| 58 | int y = KB_START_Y + KB_KEY_H / 2; | ||
| 59 | kb_result_t r = kb_hit_test(x, y, KB_ALPHA_LOWER); | ||
| 60 | ASSERT(r.action == KB_ACTION_CHAR, "Row 0 second key is a char"); | ||
| 61 | ASSERT_EQ_INT('w', r.ch, "Row 0 second key = 'w'"); | ||
| 62 | } | ||
| 63 | |||
| 64 | { | ||
| 65 | int y_row1 = KB_START_Y + (KB_KEY_H + KB_KEY_GAP) + KB_KEY_H / 2; | ||
| 66 | int x_row1 = 14 + KB_KEY_W / 2; | ||
| 67 | kb_result_t r = kb_hit_test(x_row1, y_row1, KB_ALPHA_LOWER); | ||
| 68 | ASSERT(r.action == KB_ACTION_CHAR, "Row 1 first key is a char"); | ||
| 69 | ASSERT_EQ_INT('a', r.ch, "Row 1 first key = 'a'"); | ||
| 70 | } | ||
| 71 | |||
| 72 | { | ||
| 73 | int y_row2 = KB_START_Y + 2 * (KB_KEY_H + KB_KEY_GAP) + KB_KEY_H / 2; | ||
| 74 | int x_row2 = 23 + 42 / 2; | ||
| 75 | kb_result_t r = kb_hit_test(x_row2, y_row2, KB_ALPHA_LOWER); | ||
| 76 | ASSERT(r.action == KB_ACTION_SHIFT, "Row 2 first key = SHIFT"); | ||
| 77 | } | ||
| 78 | |||
| 79 | { | ||
| 80 | kb_state_t st; | ||
| 81 | kb_state_init(&st); | ||
| 82 | ASSERT_EQ_INT(0, st.cursor, "Initial cursor = 0"); | ||
| 83 | ASSERT_EQ_INT(KB_ALPHA_LOWER, st.layer, "Initial layer = lower"); | ||
| 84 | ASSERT_EQ_STR("", st.input, "Initial input is empty"); | ||
| 85 | |||
| 86 | kb_apply(&st, (kb_result_t){KB_ACTION_CHAR, 'h'}); | ||
| 87 | ASSERT_EQ_STR("h", st.input, "After typing 'h': input='h'"); | ||
| 88 | ASSERT_EQ_INT(1, st.cursor, "After typing 'h': cursor=1"); | ||
| 89 | |||
| 90 | kb_apply(&st, (kb_result_t){KB_ACTION_CHAR, 'i'}); | ||
| 91 | ASSERT_EQ_STR("hi", st.input, "After typing 'i': input='hi'"); | ||
| 92 | |||
| 93 | kb_apply(&st, (kb_result_t){KB_ACTION_BACKSPACE, 0}); | ||
| 94 | ASSERT_EQ_STR("h", st.input, "After backspace: input='h'"); | ||
| 95 | ASSERT_EQ_INT(1, st.cursor, "After backspace: cursor=1"); | ||
| 96 | |||
| 97 | kb_apply(&st, (kb_result_t){KB_ACTION_BACKSPACE, 0}); | ||
| 98 | ASSERT_EQ_STR("", st.input, "After second backspace: empty"); | ||
| 99 | ASSERT_EQ_INT(0, st.cursor, "After second backspace: cursor=0"); | ||
| 100 | |||
| 101 | kb_apply(&st, (kb_result_t){KB_ACTION_BACKSPACE, 0}); | ||
| 102 | ASSERT_EQ_INT(0, st.cursor, "Backspace on empty stays at 0"); | ||
| 103 | } | ||
| 104 | |||
| 105 | { | ||
| 106 | kb_state_t st; | ||
| 107 | kb_state_init(&st); | ||
| 108 | |||
| 109 | kb_apply(&st, (kb_result_t){KB_ACTION_SHIFT, 0}); | ||
| 110 | ASSERT_EQ_INT(KB_ALPHA_UPPER, st.layer, "Shift: lower->upper"); | ||
| 111 | |||
| 112 | kb_apply(&st, (kb_result_t){KB_ACTION_SHIFT, 0}); | ||
| 113 | ASSERT_EQ_INT(KB_ALPHA_LOWER, st.layer, "Shift: upper->lower"); | ||
| 114 | |||
| 115 | kb_apply(&st, (kb_result_t){KB_ACTION_LAYER, 0}); | ||
| 116 | ASSERT_EQ_INT(KB_NUMSYM, st.layer, "Layer: lower->numsym"); | ||
| 117 | |||
| 118 | kb_apply(&st, (kb_result_t){KB_ACTION_LAYER, 0}); | ||
| 119 | ASSERT_EQ_INT(KB_ALPHA_LOWER, st.layer, "Layer: numsym->lower"); | ||
| 120 | } | ||
| 121 | |||
| 122 | { | ||
| 123 | kb_state_t st; | ||
| 124 | kb_state_init(&st); | ||
| 125 | |||
| 126 | for (int i = 0; i < KB_INPUT_MAX; i++) { | ||
| 127 | kb_apply(&st, (kb_result_t){KB_ACTION_CHAR, 'a' + (i % 26)}); | ||
| 128 | } | ||
| 129 | ASSERT_EQ_INT(KB_INPUT_MAX, st.cursor, "Filled to max"); | ||
| 130 | ASSERT_EQ_INT(KB_INPUT_MAX, (int)strlen(st.input), "String length = max"); | ||
| 131 | |||
| 132 | kb_apply(&st, (kb_result_t){KB_ACTION_CHAR, 'Z'}); | ||
| 133 | ASSERT_EQ_INT(KB_INPUT_MAX, st.cursor, "Overflow blocked"); | ||
| 134 | ASSERT_EQ_INT(KB_INPUT_MAX, (int)strlen(st.input), "Length unchanged after overflow"); | ||
| 135 | } | ||
| 136 | |||
| 137 | { | ||
| 138 | kb_state_t st; | ||
| 139 | kb_state_init(&st); | ||
| 140 | |||
| 141 | kb_apply(&st, (kb_result_t){KB_ACTION_SPACE, ' '}); | ||
| 142 | ASSERT_EQ_STR(" ", st.input, "Space adds space char"); | ||
| 143 | ASSERT_EQ_INT(1, st.cursor, "Space increments cursor"); | ||
| 144 | } | ||
| 145 | |||
| 146 | { | ||
| 147 | kb_state_t st; | ||
| 148 | kb_state_init(&st); | ||
| 149 | kb_result_t none = {KB_ACTION_NONE, 0}; | ||
| 150 | kb_apply(&st, none); | ||
| 151 | ASSERT_EQ_STR("", st.input, "NONE action does nothing"); | ||
| 152 | |||
| 153 | kb_apply(NULL, (kb_result_t){KB_ACTION_CHAR, 'x'}); | ||
| 154 | } | ||
| 155 | |||
| 156 | { | ||
| 157 | kb_state_t st; | ||
| 158 | kb_state_init(&st); | ||
| 159 | |||
| 160 | kb_apply(&st, (kb_result_t){KB_ACTION_CHAR, 'P'}); | ||
| 161 | kb_apply(&st, (kb_result_t){KB_ACTION_CHAR, '@'}); | ||
| 162 | kb_apply(&st, (kb_result_t){KB_ACTION_CHAR, 's'}); | ||
| 163 | kb_apply(&st, (kb_result_t){KB_ACTION_CHAR, 's'}); | ||
| 164 | ASSERT_EQ_STR("P@ss", st.input, "Password build: P@ss"); | ||
| 165 | } | ||
| 166 | |||
| 167 | TEST_SUMMARY(); | ||
| 168 | } | ||