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 /tests/unit | |
| parent | f2c4b15c72a2fa89caed5d8a11a4ea9832c48be6 (diff) | |
feat: add on-screen keyboard with layout/hit detection tests
Diffstat (limited to 'tests/unit')
| -rw-r--r-- | tests/unit/Makefile | 5 | ||||
| -rw-r--r-- | tests/unit/test_keyboard.c | 168 |
2 files changed, 172 insertions, 1 deletions
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 | } | ||